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));
    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);
    }

  好吧,看过了这段,可能有人又迷惑了,不是说有名左指变量不能绑定到右值引用上面么?为什么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构造函数!

时间: 2024-08-06 09:08:06

C++11中的右值引用及move语义编程的相关文章

C++11中的右值引用和move函数

新版的C++标准库出现了很多C++11的新特性,刚开始接触确实很费劲,特别是右值引用和move函数这种基于效率的考虑损失了语言的简单直接的特性,废话不多说,先看右值引用. C++中根据const和non_const,lvalue和rvalue可分为四类对象  non_const          const lvalue  非常量左值         常量左值 rvalue  非常量右值         常量左值 如果使用C++03标准,函数的参数选择const reference(常量引用),

C++ 11 中的右值引用

C++ 11 中的右值引用 右值引用的功能 首先,我并不介绍什么是右值引用,而是以一个例子里来介绍一下右值引用的功能: #include <iostream>    #include <vector>    using namespace std; class obj    {    public :        obj() { cout << ">> create obj " << endl; }        obj(c

[转载] C++11中的右值引用

C++11中的右值引用 May 18, 2015 移动构造函数 C++98中的左值和右值 C++11右值引用和移动语义 强制移动语义std::move() 右值引用和右值的关系 完美转发 引用折叠推导规则 特殊模板参数推导规则 解决完美转发问题 引用 在C++98中有左值和右值的概念,不过这两个概念对于很多程序员并不关心,因为不知道这两个概念照样可以写出好程序.在C++11中对右值的概念进行了增强,我个人理解这部分内容是C++11引入的特性中最难以理解的了.该特性的引入至少可以解决C++98中的

深入右值引用,move语义和完美转发

深入右值引用,move语义和完美转发 转载请注明:http://blog.csdn.net/booirror/article/details/45057689 乍看起来,move语义使得你可以用廉价的move赋值替代昂贵的copy赋值,完美转发使得你可以将传来的任意参数转发给 其他函数,而右值引用使得move语义和完美转发成为可能.然而,慢慢地你发现这不那么简单,你发现std::move并没有move任何东西,完美转发也并不完美,而T&&也不一定就是右值引用-- move语义 最原始的左值

C++11中的右值引用

原文出处:http://kuring.me/post/cpp11_right_reference May 18, 2015 移动构造函数 C++98中的左值和右值 C++11右值引用和移动语义 强制移动语义std::move() 右值引用和右值的关系 完美转发 引用折叠推导规则 特殊模板参数推导规则 解决完美转发问题 引用 在C++98中有左值和右值的概念,不过这两个概念对于很多程序员并不关心,因为不知道这两个概念照样可以写出好程序.在C++11中对右值的概念进行了增强,我个人理解这部分内容是C

【转载】C++ 11中的右值引用

本篇随笔为转载,原博地址如下:http://www.cnblogs.com/TianFang/archive/2013/01/26/2878356.html 右值引用的功能 首先,我并不介绍什么是右值引用,而是以一个例子里来介绍一下右值引用的功能: #include <iostream>    #include <vector>    using namespace std; class obj    {    public :        obj() { cout <&l

C++11之右值引用、move语义

C++11中增加了一个新的类型,即右值引用(R-value reference),标记为T&& .而它的目的就是去消除不必要的深拷贝,提高性能. 概念性的东西就不多说了.直接用代码体现其优势. 实现一个MyString类: 1 class MyString { 2 public: 3 MyString():m_data(nullptr), m_len(0) {} 4 MyString(const char* p) { 5 m_len = strlen(p); 6 copy_data(p);

C++11线程指南(四)--右值引用与移动语义

1. 按值传递 什么是按值传递? 当一个函数通过值的方式获取它的参数时,就会包含一个拷贝的动作.编译器知道如何去进行拷贝.如果参数是自定义类型,则我们还需要提供拷贝构造函数,或者赋值运算符来进行深拷贝.然而,拷贝是需要代价的.在我们使用STL容器时,就存在大量的拷贝代价.当按值传递参数时,会产生临时对象,浪费宝贵的CPU以及内存资源. 需要找到一个减少不必要拷贝的方法.移动语义就是其中一种. 2. 右值引用 此处介绍右值引用的目的,是为了实现后面的移动语义. 右值引用使得我们可以分辨一个值是左值

[转][c++11]我理解的右值引用、移动语义和完美转发

c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的持久化对象,右值是指表达式结束时就不再存在的临时对象.所有的具名变量或者对象都是左值,而右值不具名.很难得到左值和右值的真正定义,但是有一个可以区分左值和右值的便捷方法:看能不能对表达式取地址,如果能,则为左值,否则为右值. 看见书上又将右值分为将亡值和纯右值.纯右值就是c++98标准中右值的概念,