对右值引用 vector::push_back 函数如何提高效率感到困惑
Confused by how the rvalue reference vector::push_back function increases effciency
从 cppreference.com 中,我找到了一个关于使用 std::move 的简单示例:
std::string str = "Hello";
std::vector<std::string> v;
// uses the push_back(const T&) overload, which means
// we'll incur the cost of copying str
v.push_back(str); // First push_back
std::cout << "After copy, str is \"" << str << "\"\n";
// uses the rvalue reference push_back(T&&) overload,
// which means no strings will be copied; instead, the contents
// of str will be moved into the vector. This is less
// expensive, but also means str might now be empty.
v.push_back(std::move(str)); // Second push_back
评论说避免了字符串复制。
第一个push_back会调用:void push_back(const value_type& _Val)
第二个 push_back 将调用:void push_back(value_type&& _Val)
我查看了两个函数的实现代码:
void push_back(const value_type& _Val)
{ // insert element at end
if (_Inside(_STD addressof(_Val)))
{ // push back an element
size_type _Idx = _STD addressof(_Val) - _Unfancy(this->_Myfirst());
if (this->_Mylast() == this->_Myend())
_Reserve(1);
_Orphan_range(this->_Mylast(), this->_Mylast());
this->_Getal().construct(_Unfancy(this->_Mylast()),
this->_Myfirst()[_Idx]);
++this->_Mylast();
}
else
{ // push back a non-element
if (this->_Mylast() == this->_Myend())
_Reserve(1);
_Orphan_range(this->_Mylast(), this->_Mylast());
this->_Getal().construct(_Unfancy(this->_Mylast()),
_Val);
++this->_Mylast();
}
}
和
void push_back(value_type&& _Val)
{ // insert by moving into element at end
if (_Inside(_STD addressof(_Val)))
{ // push back an element
size_type _Idx = _STD addressof(_Val) - _Unfancy(this->_Myfirst());
if (this->_Mylast() == this->_Myend())
_Reserve(1);
_Orphan_range(this->_Mylast(), this->_Mylast());
this->_Getal().construct(_Unfancy(this->_Mylast()),
_STD forward<value_type>(this->_Myfirst()[_Idx]));
++this->_Mylast();
}
else
{ // push back a non-element
if (this->_Mylast() == this->_Myend())
_Reserve(1);
_Orphan_range(this->_Mylast(), this->_Mylast());
this->_Getal().construct(_Unfancy(this->_Mylast()),
_STD forward<value_type>(_Val));
++this->_Mylast();
}
}
所以,根据我的理解,第一个push_back(v.push_back(str);
)和第二个push_back(v.push_back(std::move(str));
)都会触发向量构造一个std::string
输入变量并将其附加到向量。
所以,实际上在两个 push_back 调用中,字符串都没有被复制。而且,对于两个 push_back,开销是相同的,因为两个调用基本上做同样的事情,除了第二个 push_back 会使 str
输入为空。
至于效率,我能想到的唯一区别就是第二个push_back不会调用delete[]cstring;在 std::string 的析构函数中,这使得第二个 push_back 调用更有效率。
不知道我的理解是否正确。非常感谢!
缺少像 "Small String" 这样的优化,std::basic_string
基本上包含指向字符串中字符的动态分配区域的指针。正是这个副本被避免了,因为被移动的对象只能窃取被移动的指针,而不是分配自己的指针并复制数据。
区别就在这里:
this->_Getal().construct(_Unfancy(this->_Mylast()),
_STD forward<value_type>(_Val));
对
this->_Getal().construct(_Unfancy(this->_Mylast()),
_Val);
现在 forward<value_type>
实际上调用 std::string
上的 std::move
。
在一种情况下,我们通过 std::string const&
构建 std::string
,在另一种情况下,通过 std::string &&
.
因此,要了解差异,我们必须检查这两个不同的构造函数的作用。
std::string
通常与 SBO(小缓冲区优化)一起实现。如果字符串很短(十几个字符),则字符串存储在 within the std::string
.
如果它更长,则 std::string
存储指向它的指针。
如果您启用了 SBO,则移动和复制都将字节复制过来。然后移动可能会清除源。
std::string(std::string const&)
在非 SBO 的情况下进行分配并复制包含字符的缓冲区。
std::string(std::string &&)
在非 SBO 的情况下将指针移动到目标对象,并清空源对象。没有发生内存分配,并且复制了零字节。
这是 push_back(&&)
重载提供的。
从 cppreference.com 中,我找到了一个关于使用 std::move 的简单示例:
std::string str = "Hello";
std::vector<std::string> v;
// uses the push_back(const T&) overload, which means
// we'll incur the cost of copying str
v.push_back(str); // First push_back
std::cout << "After copy, str is \"" << str << "\"\n";
// uses the rvalue reference push_back(T&&) overload,
// which means no strings will be copied; instead, the contents
// of str will be moved into the vector. This is less
// expensive, but also means str might now be empty.
v.push_back(std::move(str)); // Second push_back
评论说避免了字符串复制。
第一个push_back会调用:void push_back(const value_type& _Val)
第二个 push_back 将调用:void push_back(value_type&& _Val)
我查看了两个函数的实现代码:
void push_back(const value_type& _Val)
{ // insert element at end
if (_Inside(_STD addressof(_Val)))
{ // push back an element
size_type _Idx = _STD addressof(_Val) - _Unfancy(this->_Myfirst());
if (this->_Mylast() == this->_Myend())
_Reserve(1);
_Orphan_range(this->_Mylast(), this->_Mylast());
this->_Getal().construct(_Unfancy(this->_Mylast()),
this->_Myfirst()[_Idx]);
++this->_Mylast();
}
else
{ // push back a non-element
if (this->_Mylast() == this->_Myend())
_Reserve(1);
_Orphan_range(this->_Mylast(), this->_Mylast());
this->_Getal().construct(_Unfancy(this->_Mylast()),
_Val);
++this->_Mylast();
}
}
和
void push_back(value_type&& _Val)
{ // insert by moving into element at end
if (_Inside(_STD addressof(_Val)))
{ // push back an element
size_type _Idx = _STD addressof(_Val) - _Unfancy(this->_Myfirst());
if (this->_Mylast() == this->_Myend())
_Reserve(1);
_Orphan_range(this->_Mylast(), this->_Mylast());
this->_Getal().construct(_Unfancy(this->_Mylast()),
_STD forward<value_type>(this->_Myfirst()[_Idx]));
++this->_Mylast();
}
else
{ // push back a non-element
if (this->_Mylast() == this->_Myend())
_Reserve(1);
_Orphan_range(this->_Mylast(), this->_Mylast());
this->_Getal().construct(_Unfancy(this->_Mylast()),
_STD forward<value_type>(_Val));
++this->_Mylast();
}
}
所以,根据我的理解,第一个push_back(v.push_back(str);
)和第二个push_back(v.push_back(std::move(str));
)都会触发向量构造一个std::string
输入变量并将其附加到向量。
所以,实际上在两个 push_back 调用中,字符串都没有被复制。而且,对于两个 push_back,开销是相同的,因为两个调用基本上做同样的事情,除了第二个 push_back 会使 str
输入为空。
至于效率,我能想到的唯一区别就是第二个push_back不会调用delete[]cstring;在 std::string 的析构函数中,这使得第二个 push_back 调用更有效率。
不知道我的理解是否正确。非常感谢!
缺少像 "Small String" 这样的优化,std::basic_string
基本上包含指向字符串中字符的动态分配区域的指针。正是这个副本被避免了,因为被移动的对象只能窃取被移动的指针,而不是分配自己的指针并复制数据。
区别就在这里:
this->_Getal().construct(_Unfancy(this->_Mylast()),
_STD forward<value_type>(_Val));
对
this->_Getal().construct(_Unfancy(this->_Mylast()),
_Val);
现在 forward<value_type>
实际上调用 std::string
上的 std::move
。
在一种情况下,我们通过 std::string const&
构建 std::string
,在另一种情况下,通过 std::string &&
.
因此,要了解差异,我们必须检查这两个不同的构造函数的作用。
std::string
通常与 SBO(小缓冲区优化)一起实现。如果字符串很短(十几个字符),则字符串存储在 within the std::string
.
如果它更长,则 std::string
存储指向它的指针。
如果您启用了 SBO,则移动和复制都将字节复制过来。然后移动可能会清除源。
std::string(std::string const&)
在非 SBO 的情况下进行分配并复制包含字符的缓冲区。
std::string(std::string &&)
在非 SBO 的情况下将指针移动到目标对象,并清空源对象。没有发生内存分配,并且复制了零字节。
这是 push_back(&&)
重载提供的。