对右值引用 vector::push_back 函数如何提高效率感到困惑

lig*_*rek 3 c++ move vector rvalue-reference c++11

从 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
Run Code Online (Sandbox Code Playgroud)

评论说避免了字符串复制。

第一个 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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

所以,根据我的理解,两者都先push_back(v.push_back(str); )和第二次push_back( v.push_back(std::move(str));)都会触发vector构造一个std::string类型变量,并把它附加到vector上。

因此,实际上在两次 push_back 调用中,都没有复制字符串。而且,对于两个 push_back,开销是相同的,因为两个调用基本上都做同样的事情,除了第二个 push_back 会使str输入为空。

至于效率,我能想到的唯一区别就是第二次push_back不会调用delete[]cstring;在 std::string 的析构函数中,这使得第二次 push_back 调用更有效率。

不确定我的理解是否正确。非常感谢!

Yak*_*ont 5

区别在这里:

    this->_Getal().construct(_Unfancy(this->_Mylast()),
        _STD forward<value_type>(_Val));
Run Code Online (Sandbox Code Playgroud)

对比

    this->_Getal().construct(_Unfancy(this->_Mylast()),
        _Val);
Run Code Online (Sandbox Code Playgroud)

现在forward<value_type>实际上调用std::movestd::string.

在一种情况下,我们构造一个std::stringby std::string const&,在另一种情况下构造a std::string &&

因此,要查看差异,我们必须检查这两个不同的构造函数的作用。

std::string通常使用 SBO(小缓冲区优化)来实现。如果字符串是短(十几个字符),则该字符串被存储std::string

如果它更长,则std::string存储指向它的指针。

如果 SBO 处于活动状态,则移动和复制都将字节复制过来。移动然后可以清除源。

std::string(std::string const&) 在非 SBO 的情况下,会进行分配并复制包含字符的缓冲区。

std::string(std::string &&)在非 SBO 的情况下,将指针移到目标对象上,并清空源对象。不发生内存分配,并且复制零字节。

这就是push_back(&&)重载所提供的。