zoukankan      html  css  js  c++  java
  • C++11--右值引用

    1、左值、右值

      左值、右值是表达式的属性,单独一个变量也是表达式,称为变量表达式,它是最简单的表达式,没有运算符,只有一个运算对象。

      左值表达式即为返回左值的表达式,他们有:变量表达式、前++(前--)运算、*解引用运算、[]下标运算、返回左值引用类型的函数表达式。

      右值表达式即为返回右值的表达式,如:字面常量、非左值运算的运算表达式(如100 * i、i + n等表达式)、返回非左值引用类型的函数表达式。

      从以上可知左值持久,右值短暂,左值表达式表示的是一个对象的身份,而右值表达式表示的是对象的值,只有左值才能被赋值,如:*p = 1,++i = 0,ary[0] = 100。

    2、左值引用

      我们原来学过使用&来定义引用类型,其实使用&定义的是左值引用,因为绑定的都是左值表达式,如:

    int o = 5;
    int& h = o; //o是变量(变量表达式)
    
    int& e = ++o; //前++运算
    
    int* p = &o;
    int& f = *p;  //*解引用运算
    
    int ary[10] = { 0 };
    int& s = ary[0]; //[]下标运算
    
    int& func(){...}
    int&q = func(); //func()是返回引用类型的函数表达式

      &定义的引用类型不能用右值表达式来初始化,即左值引用不能绑定右值表达式,如:

    int& m = 100; //错误,将左值引用绑定到字面常量上
    
    int j = 100;
    int& n = j + 50; //错误,将左值引用绑定到右值表达式上
    
    int func() { ... }
    int& x = func(); //错误,将左值引用绑定到返回非左值引用类型的函数表达式

     左值引用&只能使用左值表达式来初始化即只能绑定左值表达式,那右值表达式使用哪种类型呢?答案就是C++11中增加的右值引用。

    3、右值引用

      右值引用只能绑定到临时对象,该临时对象将要被销毁且该对象没有其他用户,右值引用使用&&来定义,如:

    int&&r = 100; //将右值引用绑定到字面常量上,即使用字面常量来初始化右值引用
    
    int k = 5;
    int&& n = k + 10; //将右值引用绑定到右值表达式上
    
    int func() { ... }
    int&& x = func(); //将右值引用绑定到返回非左值引用类型的函数表达式

      我们可以通过std::move()函数来将一个右值引用绑定到左值上,为了避免冲突,对于move()函数我们应该在调用的时候总是加上命名空间std::,如下所示。

    int i = 100;
    int&& r = std::move(i);

      因为右值引用只能绑定到临时对象上,所以对左值调用std::move()后,除非对原左值赋新值或销毁它外,我们将不再使用它,比如下面的代码,unique_ptr有一个构造函数支持右值引用,当我们执行这个构造函数后u1不再拥有原始指针,因为在这个构造函数中会将u2保存u1中的原始指针后将u1中的指针设为nullptr,这就保证了通过u1不再会使用原来的值,我们通过reset()对u1赋新值后可以再使用它。

        std::unique_ptr<int> u1(new int);
        std::unique_ptr<int> u2(std::move(u1));
        cout << "u1: " << (u1 ? "not null" : "null") << endl; //输出为 "u1: null"
        u1.reset(new int);

      我们知道左值引用的一个重要作用就是声明函数的参数,避免值传递和拷贝参数对象,右值引用同样是提供这个功能,我们称右值引用可以移动对象而非拷贝对象,而且它针对的是传入的参数是右值的情况,如:

    void func(int&& i) {}
    void test(std::string&& str) {}
    
    func(100); //避免值传递拷贝参数
    
    int k = 100;
    func(k + 5); //避免值传递拷贝参数
    
    test(std::string("abc")); //避免值传递拷贝参数

     4、拷贝和移动对象

      右值引用参数在类的构造函数和成员函数中用的比较多,比如C++11中很多类的构造函数、成员函数除了拷贝版本外还增加了一个移动版本,如string::string(string&& r)、vector<value_type>::push_back(value_type&& var),区分移动和拷贝的重载函数即通过函数的参数:拷贝版本通常是const T&,而移动版本是一个T&&(因为我们要从实参"窃取"数据,所以不是const T&&)。

      我们可以让自己的类也支持移动操作,通过为其定义移动构造函数和移动赋值运算符。为了完成资源移动必须确保移动后源对象的销毁是无害的,移动完成后源对象的资源所有权已经归属新创建的对象。

      由于移动操作是“窃取”资源,它通常不非配任何资源,所以我们通常都应该使用noexcept来声明移动函数不会抛出异常,而且对于标准容器来说,如果不为移动构造函数标记为noexcept的话,容器会使用拷贝构造函数而非移动构造函数。

      下面为类中添加移动构造函数和移动赋值运算符的一个例子:

    class CFoo
    {
    public:
        CFoo(const CFoo& lf)
        {
            if (lf.m_pData)
            {
                m_pData = new char(iDataSize);
                memcpy(m_pData, lf.m_pData, iDataSize);
            }
            else
            {
                if (m_pData)
                {
                    delete[] m_pData;
                    m_pData = nullptr;
                }
            }
        }
        CFoo(CFoo&& rf)noexcept
        {
            m_pData = rf.m_pData; //从源对象“窃取”资源
            rf.m_pData = nullptr; //防止源对象析构后资源失效,源对象可安全销毁
        }
        CFoo& operator=(const CFoo& rhs)
        {
            if (&rhs == this)
                return *this;
    
            if (m_pData)
                delete[] m_pData;
    
            if (rhs.m_pData)
            {
                m_pData = new char[iDataSize];
                memcpy(m_pData, rhs.m_pData, iDataSize);
            }
            else
            {
                if (m_pData)
                {
                    delete[] m_pData;
                    m_pData = nullptr;
                }
            }
            
            return *this;
        }
        CFoo& operator=(CFoo&& rhs)noexcept
        {
            if (&rhs == this)
                return *this;
            m_pData = rhs.m_pData; //从源对象“窃取”资源
            rhs.m_pData = nullptr; //防止源对象析构后资源失效,源对象可安全销毁
            return *this;
        }
        virtual ~CFoo()
        {
            if (m_pData)
            {
                delete[] m_pData;
                m_pData = nullptr;
            }
        }
    private:
        char* m_pData = nullptr;
        int iDataSize = 0;
    };

      5、引用限定符&和&&

      &用来限定函数只能被左值对象来调用,&&用来限定函数只能被又值对象来调用,需要注意的是&和&&只能用于非static的成员函数,且必须同时出现在函数的声明和定义中,在const限定符之后,如:

    class CFoo
    {
    public:
        void fun()&{}
    };
    
    CFoo().fun(); //错误,fun()只能被左值对象来调用
    CFoo f;
    f.func(); //正确
    
    //======================================
    
    class CShow
    {
    public:
        CShow& operator=(const CShow& rhs)&&
        {
            return *this;
        }
    };
    
    CShow s;
    s = CShow(); //错误,operator=只能被右值对象调用
    CShow() = s; //正确

       6、C++11中新增的使用{}初始化

      原来在C++中,对于普通数组、结构体、没有构造析构和虚函数的类可以使用 {} 进行初始化,也就是我们所说的初始化列表,而对于类对象或容器的初始化不支持{},只能使用()或拷贝构造函数,C++11中则没有了这个限制,使用初始化列表还可以防止类型收窄:

    int i = { 100 };
        int num{ 100 };
        int n{ 100.0 }; //从double转换为int为类型收缩,编译出错
    
        std::vector<int> v = { 0, 1, 2 };
        std::map<int, std::string> m = { { 0, "c++" },{ 1, "java" },{ 2, "c#" } };
        int* p = new int[3]{ 0, 1, 2 };
    
        class CFoo
        {
        public:
            CFoo(int a, int b) { ; }
        };
    
        class CBar
        {
        private:
            CFoo f{ 0, 0 };
        };
    
        CFoo f = { 8, 9 };
    
        CFoo func(CFoo f)
        {
            return{ 8, 9 };
        }
    
        func({ 10, 11 });
  • 相关阅读:
    简单爬虫架构解析
    三种urllib实现网页下载,含cookie模拟登陆
    MySQL 从入门到删库
    Python Set
    Python dict
    Python tuple
    Python List
    死锁问题
    线程通信之生产者和消费者案例
    多线程安全和线程同步
  • 原文地址:https://www.cnblogs.com/milanleon/p/8655608.html
Copyright © 2011-2022 走看看