[C++] C++11右值引用与移动语义揭秘

1. 左值、右值、左值引用和右值引用

C++98中就有引用语法了(左值引用),C++11中又增加了右值引用的语法特性。

1.1 左值和右值

左值和右值的区别如下:

  1. 左值可以取地址,右值不能取地址
  2. 左值在内存中存储,右值一般存在栈 / 寄存器 / 临时位置中

常见的左值和右值:

cpp 复制代码
//左值
int* p = new int(0);
int b = 1;
const int c = b;
*p = 10;
string s("111111");
s[0] = 'x';

//右值
double x = 1.1, y = 2.2;
10;
fmin(x + y);
string("11111");

1.2 左值引用和右值引用

左值引用是给左值取别名,右值引用同理。

cpp 复制代码
//左值引用
string& si = s;
int*& pi = p;

//右值引用
double&& z = x + y;
int&& num = 10;

左值引用在特定条件下可以绑定右值,右值引用同样也能绑定左值:

cpp 复制代码
const int& ci = 10; //左值引用->右值(权限缩小)
double&& f = move(x); //右值引用->左值
  • move内部进行了强制类型转换和引用折叠,使得左值可以转化为右值返回
  • 左值引用和右值引用的底层都是指针
  • 右值引用和const左值引用可以为临时对象延长生命周期
cpp 复制代码
//延长生命周期
int main()
{
    string s1 = "test";
    const string& t1 = s1 + s1;
    string&& t2 = s1 + s1;
    cout << t1 << endl;
    cout << t2 << endl;
    return 0;
}

1.3 参数匹配

左值和右值还支持参数匹配,其中右值引用的参数类型是左值。可以通过函数重载来测试:

cpp 复制代码
//参数匹配
void f(int& x)
{
    std::cout << "左值引用重载 f(" << x << ")\n";
}
void f(const int& x)
{
    std::cout << "const左值引用重载 f(" << x << ")\n";
}
void f(int&& x)
{
    std::cout << "右值引用重载 f(" << x << ")\n";
}
int main()
{
    int x = 10;
    int y = 20;
    const int z = 30;
    int&& t = 40; //右值引用的参数类型是左值
    f(x);
    f(15);
    f(z);
    f(std::move(x));
    f(t); 
    return 0;
}

输出结果:

1.4 总结

引用的价值:

  1. 减少拷贝,提高效率
  2. 左值引用还能修改实参,使用方便

左值引用的短板:

局部对象出了函数作用域就销毁,不能返回其左值引用,只能传值返回(有拷贝开销)。

三种解决方案:

  1. 输出型参数:结果通过参数带出 ------ 牺牲可读性
  2. 编译器优化(RVO):省略拷贝,但非标准保证
  3. 右值引用 + 移动语义(C++11):标准方案,直接掠夺临时对象资源,避免深拷贝

2. 右值引用和移动语义

2.1 移动构造和移动赋值

  1. 参数都是右值引用
  2. 都是为右值服务,可以省去拷贝构造,直接掠夺资源(偷指针后置空),提升效率
cpp 复制代码
// 移动构造
string(string&& s)
{
    cout << "string(string&& s) -- 移动构造" <<  endl;
    swap(s);
}

// 移动赋值
string& operator=(string&& s)
{
    cout << "string& operator=(string&& s) -- 移动赋值" << endl;
    swap(s);
    return *this;
}

2.2 移动语义对传值返回和传参的优化

我们以自己实现的string类和addStrings函数为例:

cpp 复制代码
namespace mzh
{
    class string
    {
    public:
        typedef char* iterator;
        typedef const char* const_iterator;
        friend ostream& operator<<(ostream& out, const string& s);


        iterator begin()
        {
            return _str;
        }
        iterator end()
        {
            return _str + _size;
        }

        const_iterator begin() const
        {
            return _str;
        }
        const_iterator end() const
        {
            return _str + _size;
        }

        string(const char* str = "")
            :_size(strlen(str))
            , _capacity(_size)
        {
            cout << "string(char* str)-构造" <<  endl;
            _str = new char[_capacity + 1];
            strcpy(_str, str);
        }

        void swap(string& s)
        {
            ::swap(_str, s._str);
            ::swap(_size, s._size);
            ::swap(_capacity, s._capacity);
        }

        string(const string& s)
            :_str(nullptr)
        {
            cout << "string(const string& s) -- 拷贝构造" <<  endl;
            reserve(s._capacity);
            for (auto ch : s)
            {
                push_back(ch);
            }
        }

        // 移动构造
        string(string&& s)
        {
            cout << "string(string&& s) -- 移动构造" <<  endl;
            swap(s);
        }

        //拷贝赋值
        string& operator=(const string& s)
        {
            cout << "string& operator=(const string& s) -- 拷贝赋值" << endl;
            if (this != &s)
            {
                _str[0] = '\0';
                _size = 0;
                reserve(s._capacity);
                for (auto ch : s)
                {
                    push_back(ch);
                }
            }
            return *this;
        }

        // 移动赋值
        string& operator=(string&& s)
        {
            cout << "string& operator=(string&& s) -- 移动赋值" << endl;
            swap(s);
            return *this;
        }

        ~string()
        {
            // cout << "~string() -- 析构" << endl;
            delete[] _str;
            _str = nullptr;
        }

        char& operator[](size_t pos)
        {
            assert(pos < _size);
            return _str[pos];
        }

        void reserve(size_t n)
        {
            if (n > _capacity)
            {
                char* tmp = new char[n + 1];
                if (_str)
                {
                    strcpy(tmp, _str);
                    delete[] _str;
                }
                _str = tmp;
                _capacity = n;
            }
        }

        void push_back(char ch)
        {
            if (_size >= _capacity)
            {
                size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
                reserve(newcapacity);
            }
            _str[_size] = ch;
            ++_size;
            _str[_size] = '\0';
        }

        string& operator+=(char ch)
        {
            push_back(ch);
            return *this;
        }

        const char* c_str() const
        {
            return _str;
        }

        size_t size() const
        {
            return _size;
        }

    private:
        char* _str = nullptr;
        size_t _size = 0;
        size_t _capacity = 0;
    };

    ostream& operator<<(ostream& out, const string& s)
    {
        for(size_t i=0; i<s._size; i++)
        {
            out << s._str[i];
        }
        return out;
    }
}
cpp 复制代码
namespace mzh
{
    string addStrings(string num1, string num2)
    {
        string str;
        int end1 = num1.size() - 1, end2 = num2.size() - 1;
        int next = 0;
        while (end1 >= 0 || end2 >= 0)
        {
            int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
            int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;
            int ret = val1 + val2 + next;
            next = ret / 10;
            ret = ret % 10;
            str += ('0' + ret);
        }
        if (next == 1)
            str += '1';
        reverse(str.begin(), str.end());
        cout << "******************************" << endl;
        return str;
    }
}

2.2.1 移动构造的优化

示例main函数:

cpp 复制代码
int main()
{
    mzh::string ret = mzh::addStrings("11111", "2222");
    cout << ret.c_str() << endl;
    return 0;
}

移动构造:

移动构造:

2.2.1 移动赋值的优化

示例main函数:

cpp 复制代码
//场景2
int main()
{
mzh::string ret; //1构造
ret = mzh::addStrings("11111", "2222");
cout << ret.c_str() << endl;
return 0;
}

移动赋值:

移动赋值:

2.3 总结

C++11 前后对比:

  1. 前:传值返回依赖编译器优化,非标准保证
  2. 后:语言标准提供移动构造/移动赋值,编译器优化可有可无,差别不大;STL容器新增了移动构造/移动赋值,大大提效

值得实现移动语义的类型:

  1. 深拷贝类(如 vector/string/map...):实现移动构造和移动赋值,价值很大
  2. 浅拷贝类(如 Date/pair<int,int>...):不需要实现,代价不大

3. 类型分类

文档:值类别

右值分类:

  1. 纯右值(无地址)
  2. 将亡值(有地址,可以偷指针)

4. 引用折叠

通过模板或者typedef可以实现引用的引用:

cpp 复制代码
template<class T>
void f1(T& x)
{}

template<class T>
void f2(T&& x)
{}

int main()
{
    typedef int& lref;
    typedef int&& rref;
    int n = 0;
    lref& r1 = n; //左 + 左 = 左
    lref&& r2 = n; //左 + 右 = 左
    rref& r3 = n; //右 + 左 = 左
    rref&& r4 = 1; //右 + 右 = 右
}
  1. & & → &
  2. & && → &
  3. && & → &
  4. && && → &&

总结:只要有左值引用参与,就是左值引用;只有两个右值引用叠加时才是右值引用。

引用折叠在实践中常用于函数自动推导类型:

cpp 复制代码
//引用折叠/万能引用
template<class T>
void Function(T&& t)
{
    int a = 0;
    T x = a;
    cout << &a << endl;
    cout << &x << endl;
    cout << endl;
}
int main()
{
    Function(10); //右值:T为int
    int a;
    Function(a); //左值:T为int&
    Function(std::move(a)); //右值:T为int
    const int b = 8;
    Function(b); //左值:T为const int&
    Function(std::move(b)); //右值:T为const int
}

万能引用Function函数在实例化后,参数可以是左值引用,也可以是右值引用

5. 完美转发

在使用万能引用时,右值引用的参数可能需要继续向下一个函数传递:

cpp 复制代码
void push_back(const string& s)      // 左值版本
{
    // 拷贝构造插入
}

void push_back(string&& s)           // 右值版本
{
    // 插入时必须 move/forward,否则 s 在本函数内是左值
    insert(end(), move(s));  //转发为右值
    // insert(end(), s); //丢失右值属性
}

完美转发 :使用forward模板进行转发,保持参数属性不变,本质通过引用折叠实现

。模板如下:

cpp 复制代码
template <class T> T&& forward (typename remove_reference<T>::type& arg);
template <class T> T&& forward (typenameremove_reference<T>::type&& arg);

测试示例:

cpp 复制代码
//完美转发
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const右值引用" << endl; }

template<class T>
void Function(T&& t)
{
    // Fun(t);
    Fun(std::forward<T>(t)); //完美转发
}

int main()
{
    Function(10); // 右值
    int a;
    Function(a); // 左值
    Function(std::move(a)); // 右值
    const int b = 8;
    Function(b); // const左值
    Function(std::move(b)); // const右值
		return 0;
}
相关推荐
2601_966949653 分钟前
从轮询到策略消费:量化系统如何高效处理五档盘口数据
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
莫生灬灬4 分钟前
灵码 LingBuilder:用中文写代码,确定性生成真实 C++ 工程(开源)
c++·开源·mfc
浅念-6 分钟前
C++ Protobuf 入门实战:基于通讯录项目吃透proto3语法与序列化
linux·服务器·网络·c++·protobuf·proto·proto3
我命由我1234532 分钟前
人脸识别 - 去重时间窗口
java·开发语言·python·学习·java-ee·学习方法·python3.11
落魄大学生之流水线上谋生计1 小时前
Java并发核心机制详解:线程池、CAS、AQS、锁升级
java·开发语言·数据库
hahaha60161 小时前
HLS高层次综合设计技巧--数组
开发语言·嵌入式硬件·算法·fpga开发
佳児素花痴╮1 小时前
Qt速通2
开发语言·qt
“AI国潮设计-小江”2 小时前
【Python/SDXL实战】潮汕国潮IP视觉落地:普宁英歌舞猫IP & 创意甜品设计(附ComfyUI工作流与商业授权说明)
开发语言·人工智能·python·prompt·aigc
529宝宝起名网2 小时前
用 Python 分析汉字字源与起名用字的关联规律:从六书结构到文化寓意的起名偏好洞察
开发语言·python
汉克老师2 小时前
CSP-J 初赛(以满分为目标):第三十九课《数学与逻辑②——阶乘、排列数与组合数:公式到底从哪里来?》
c++·csp-j·小学生·学c++编程