C++11中的右值引用及move语义编程

C++11中的右值引用及move语义编程

  C++0x中加入了右值引用,和move函数。右值引用出现之前我们只能用const引用来关联临时对象(右值)(造孽的VS可以用非const引用关联临时对象,请忽略VS),所以我们不能修临时对象的内容,右值引用的出现就让我们可以取得临时对象的控制权,终于可以修改临时对象了!而且书上说配合move函数,可以大大提高现有C++的效率。那么是怎样提高它的效率的呢?看段代码先!

复制代码
#include <iostream>
#include <utility>
#include <vector>
#include <string>
int main()
{
    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);
    std::cout << "After copy, str is \"" << str << "\"\n";
 
    // uses the rvalue reference push_back(T&&) overload, 
    // which means no strings will 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));   //注意: void push_back( T&& value ); 
    std::cout << "After move, str is \"" << str << "\"\n";
 
    std::cout << "The contents of the vector are \"" << v[0]
                                         << "\", \"" << v[1] << "\"\n";
}

结果:
After copy, str is "Hello"
After move, str is ""
The contents of the vector are "Hello", "Hello"
 
复制代码

  看完大概明白一点儿了,加上move之后,str对象里面的内容被"移动"到新的对象中并插入到数组之中了,同时str被清空了。这样一来省去了对象拷贝的过程。所以说在str对象不再使用的情况下,这种做法的效率更高一些!但问题是str的内容在什么地方被移走的呢?move函数到底是干啥的?扣一下stl源码吧,下面是move模板的源码:

复制代码
// TEMPLATE FUNCTION move
    template<class _Ty> inline
    typename tr1::_Remove_reference<_Ty>::_Type&&
        move(_Ty&& _Arg)
    {    // forward _Arg as movable
    return ((typename tr1::_Remove_reference<_Ty>::_Type&&)_Arg); //从这里到返回值的时候,发生了string的移动拷贝构造函数调用,故字符串转移到了右值引用变量中去了。
    }
复制代码

  好吧,看过了这段,可能有人又迷惑了,不是说有名左指变量不能绑定到右值引用上面么?为什么move函数的参数是右值引用却可以接受左值变量作为参数?难道STL错了么?事实上,C++0x在引入右值引用的时候对函数模板自动推导也加入了新的规则,简单的说,像例子中的这种情况,模板参数是_Ty而函数的参数是_Ty&&(右值引用),同时_Arg是string的左值对象的情况下,会触发一个特殊规则,_Ty会推导成string&,也就是说此事推导出来的函数与move<string&>一致。那么move(_Ty&& _Arg) 得到的应该是move(string& && _Arg)这个时候根据引用折叠原则,会变成这个样子move(string& _Arg)。详细的描述参见白云飘飘翻译的vc技术文档(http://www.cppblog.com/kesalin/archive/2009/06/05/86851.html)。函数的返回值嘛,就好说了,就是返回所持有类型的右值引用了。所以,move函数的作用很简单,不管你给什么参数,都返回对应类型的右值引用!那么,上面例子中str的不是在move函数中被移走的。综上,我们猜测str内容肯定是在构造新对象的过程中被新对象偷走的,也就是在string的参数为右值引用的构造函数中被偷走的!翻看string的源码(来自VS实现的STL),果然如此啊!如下:

 

复制代码
basic_string(_Myt&& _Right)
        : _Mybase(_STD forward<_Alloc>(_Right._Alval))
        {    // construct by moving _Right
        _Tidy();
        assign(_STD forward<_Myt>(_Right));
        }

        _Myt& assign(_Myt&& _Right)
        {    // assign by moving _Right
        if (this == &_Right)
            ;
        else if (get_allocator() != _Right.get_allocator()
            && this->_BUF_SIZE <= _Right._Myres)
            *this = _Right;
        else
            {    // not same, clear this and steal from _Right
            _Tidy(true);
            if (_Right._Myres < this->_BUF_SIZE)
                _Traits::move(this->_Bx._Buf, _Right._Bx._Buf,
                    _Right._Mysize + 1);
            else
                {    // copy pointer
                this->_Bx._Ptr = _Right._Bx._Ptr;
                _Right._Bx._Ptr = 0;
                }
            this->_Mysize = _Right._Mysize;
            this->_Myres = _Right._Myres;

            _Right._Tidy();
            }
        return (*this);
        }
复制代码

 

  所以,我们知道了,C++0x在STL模板库中加入了参数为右值引用的构造函数,用于把参数所关联对象中的数据移动到新对象当中,避免了深度拷贝,增加了效率。再详细翻看源码,可以发现除了构造函数,operator=也重载了一个参数为右值引用的函数,用途和构造函数类似。所以我们自定义中的类也应该增加参数为右值引用的构造函数和重载赋值运算符!原因是啥,看例子!

未定义参数为右值引用的构造函数:

 

复制代码
#include <iostream>
#include <utility>
#include <vector>
#include <string>

using namespace std;

class MyPoint{
public:
    MyPoint()
        :comment(""), x(0), y(0)
    {
    }

    MyPoint(const MyPoint& p)
       :comment(p.comment),x(p.x),y(p.y) 
    {}

    //MyPoint(MyPoint&& p)
    //    :comment(move(p.comment)), x(p.x), y(p.y)
    //{
    //    p.x = 0;
    //    p.y = 0;
    //}

    string toString()
    {
        char buf[100];
        sprintf(buf, "%s: %d %d", comment.c_str(), x, y);

        return buf;
    }

    string comment;
    int x;
    int y;

};

int main()
{
    MyPoint p;
    p.comment = "First point";
    p.x = 9;
    p.y = 7;

    vector<MyPoint> v;
 
    v.push_back(p);

    cout << "After copy, str is \"" << p.toString() << "\"\n";
 
    v.push_back(move(p));
    cout << "After move, str is \"" << p.toString() << "\"\n";
 
    cout << "The contents of the vector are \"" << v[0].toString()
                                         << "\", \"" << v[1].toString() << "\"\n";


    cin.get();
}

结果:
After copy, str is "First point: 9 7"
After move, str is "First point: 9 7"
The contents of the vector are "First point: 9 7", "First point: 9 7"

定义了参数为右值引用的构造函数之后:
 
   
After copy, str is "First point: 9 7"
After move, str is ": 0 0"
The contents of the vector are "First point: 9 7", "First point: 9 7"
复制代码

 

  综上所述,C++0x中的move语义编程,不仅仅是在应用的时候使用参数中加上move,对于自定义类需要增加参数为右值引用的构造函数和赋值运算符,这种构造函数我们称为move构造函数!

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
右值引用和move语义是C++ 11重要的特性之一,可以提高程序的效率和性能。右值引用是一种新的引用类型,其绑定到临时对象或将要销毁的对象上,而不是左值对象。move语义则是利用右值引用,将一个对象的资源所有权从一个对象转移到另一个对象,避免了不必要的内存拷贝,提高了程序的效率。 下面是一个使用右值引用和move语义的例子: ```c++ #include <iostream> #include <vector> using namespace std; vector<int> getVector() { vector<int> v = {1, 2, 3, 4}; return v; } int main() { vector<int> v1 = getVector(); // 拷贝构造函数 vector<int> v2 = move(v1); // 移动构造函数 cout << "v1 size: " << v1.size() << endl; // 输出 0 cout << "v2 size: " << v2.size() << endl; // 输出 4 return 0; } ``` 在上面的例子,getVector函数返回一个临时对象vector<int>,该临时对象是一个右值。在主函数,我们使用拷贝构造函数将临时对象的值拷贝到v1,然后使用move函数将v1的值移动到v2。由于move函数使用了右值引用,将v1的资源所有权转移到了v2,避免了不必要的内存拷贝,提高了程序的效率。最后,我们输出v1和v2的大小,可以看到v1的大小为0,v2的大小为4,说明资源已经成功转移。 需要注意的是,使用move语义之后,原对象的值会被移动到新对象,并且原对象的值会被置为默认值(例如,对于vector而言,原对象的大小为0)。如果需要保留原对象的值,则需要在移动之前先进行一次拷贝操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值