Google C++ Coding Style:右值引用(Rvalue Reference)

简介: 右值引用是一个C++11特性,标记为T&&。GSG中定义:只为移动建构函数(Move constructor)和移动赋值操作(Move assignment)使用右值引用。
+关注继续查看

右值引用是一个C++11特性,标记为T&&。GSG中定义:只为移动建构函数(Move constructor)和移动赋值操作(Move assignment)使用右值引用。并且不要使用std::Forward(提供的完美转发特性)。

C++中右值指表达式结束时就不再存的临时对象。在C++11中,右值分为纯右值(即原始字面量,表达式产生的临时变量等),以及一个将亡值(expiring value, 使用<<深入应用C++11>>中的译法,指的是与右值引用相关的表达式,如将被移动的对象,T&&函数返回值等)。

以函数返回值表达不出右值引用的威力,因为编译的本身的优化会解决不必要的对象复制操作。而作为函数参数,如果使用const T&之类的形式也能够有效避免不必要的对象拷贝。这里特别以与标准容器配合,体现一下,右值引用最大的价值:避免深拷贝。

// 下面一个完整提供了Move Constructor和Move assignment的类。
#include <iostream>
#include <string>
#include <vector>
#include <string.h>

class Foo {
 private:
    int x = 0;
    int y = 0;
    char* strPtr = nullptr;
 public:
    Foo() {
        std::cout << "Constructor was called." << std::endl;
    }

    Foo(const char* s) {
        std::cout << "Constructor with string:" << s << std::endl;
        if (s != nullptr) {
          strPtr = new char[strlen(s)];
          strcpy(strPtr, s);
        }
    }

    // Copy constructor
    Foo(const Foo& a) : x(a.x),
                        y(a.y) {
        // Deep copy
        copyStringValue(a.strPtr);
        std::cout << "Copy constructor was called." << std::endl;
    }

    // Move constructor, no need copy string in deep.
    Foo(Foo&& a) : x(a.x),
                   y(a.y),
                   strPtr(a.strPtr) {
        a.strPtr = nullptr;  // 注意要清掉之前的字串,这样才是移动。
        std::cout << "Move constructor was called." << std::endl;
    }

    Foo& operator=(const Foo& a) {
        x = a.x;
        y = a.y;
        copyStringValue(a.strPtr);
        std::cout << "Assignment Operator was called." << std::endl;
    }

    ~Foo() {
        if (strPtr != nullptr) {
            std::cout << "Free allocated string:" << strPtr << std::endl;
            delete strPtr;
        }
        std:: cout << "Deconstructor was called." << std::endl;
    }

 private:
    void copyStringValue(const char* s) {
        if (strPtr != nullptr) {
            delete strPtr;
            strPtr = nullptr;
        }

        if (s != nullptr) {
            strPtr = new char[strlen(s)];
            strcpy(strPtr, s);
        }
    }
};

int main(void) {
    {
      std::cout << "Need to clear string twice:" << std::endl;
      std::vector<Foo> myVec;
      Foo a("Instance A");
      myVec.push_back(a);
    }

    std::cout << "============" << std::endl;

    {
      std::cout << "Only need to clear string one time:" << std::endl;
      std::vector<Foo> myVec;
      Foo c("Instance C");
      myVec.push_back(std::move(c));
    }

    std::cout << "============" << std::endl;
    {
        Foo d("Instance D");
        Foo x = d;
    }

    std::cout << "============" << std::endl;
    {
        Foo e("Instance E");
        Foo&& y = std::move(e);
    }
}

观察代码删除字串的次数,就可以了解右值引用的作用了。程序运行的输出如下:

Need to clear string twice:
Constructor with string:Instance A
Copy constructor was called.
Free allocated string:Instance A
Deconstructor was called.
Free allocated string:Instance A
Deconstructor was called.
============
Only need to clear string one time:
Constructor with string:Instance C
Move constructor was called.
Deconstructor was called.
Free allocated string:Instance C
Deconstructor was called.
============
Constructor with string:Instance D
Copy constructor was called.
Free allocated string:Instance D
Deconstructor was called.
Free allocated string:Instance D
Deconstructor was called.
============
Constructor with string:Instance E
Free allocated string:Instance E
Deconstructor was called.

但是考虑到右值引用中的引用折叠(reference collapsing)会引入一些复杂度(左右值的转换规则),造成理解上的问题,所以将右值引用的应用范围做了如开篇所说的限定。

在实际应用中,会出现没有直接定义类型的右值引用,被称为universal reference,需要进行类型推导。另一种情况是使用auto &&定义的也是universal reference。

关于std::forward,它被称为完美转发(Perfect Forwarding)。要解决的问题是在函数模板中,完全依照模板的参数的类型,保持参数的左值,右值特征),将参数传递给函数模板中调用的另一个函数(转自<<深入应用C++11>>)。根据这个定义,完美转发仅针对需要调用内部实现的模板函数,而且需要开发者识别出哪些情况是有效的,而哪些情况下又是无效的。比如适用的场景:

template<class T>
void foo(T&& arg) 
{
  // 如下保持arg的类型传入到bar()中
  bar(std::forward<T>(arg));
}

但如果内部函数无需要针对左值或右值做特殊处理,这种场景是不需要转发的。参考:When not to use std::forward

目录
相关文章
|
1月前
|
编译器 C++ 容器
【C++11】右值引用和移动语义 万能引用和完美转发(二)
【C++11】右值引用和移动语义 万能引用和完美转发(二)
33 0
|
1月前
|
存储 编译器 C++
【C++11】右值引用和移动语义 万能引用和完美转发(一)
【C++11】右值引用和移动语义 万能引用和完美转发(一)
25 0
|
2月前
|
存储 编译器 C++
【C++】C++11——右值引用和移动语义|可变参数模板
C++11新特性——右值引用和移动语义的用法及其原理
|
2月前
|
存储 安全 算法
02-📝C++核心语法|C++对C的扩展【::作用域运算符、名字控制、struct类型加强、C/C++中的const、引用(reference)、函数】
复习`C++核心语法`,且适当进行汇编探索底层实现原理,进一步夯实基础,为以后的`底层开发`、`音视频开发`、`跨平台开发`、`算法`等方向的进一步学习埋下伏笔。
02-📝C++核心语法|C++对C的扩展【::作用域运算符、名字控制、struct类型加强、C/C++中的const、引用(reference)、函数】
|
3月前
|
安全 编译器 程序员
C++11之右值引用:移动语义和完美转发(带你了解移动构造函数、纯右值、将亡值、右值引用、std::move、forward等新概念)
C++11之右值引用:移动语义和完美转发(带你了解移动构造函数、纯右值、将亡值、右值引用、std::move、forward等新概念)
47 0
|
4月前
|
存储 安全 编译器
【C++11】第一篇:琐碎知识+右值引用
在C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定。
|
5月前
|
C++ 容器
C++之右值引用
C++进阶之右值引用
77 0
|
5月前
|
存储 编译器 C++
【C++11】右值引用和移动语义
目录 右值引用和移动语义 1.1 左值引用和右值引用 1.1.1 左值和左值引用 1.1.2 右值和右值引用 1.2 左值引用与右值引用比较 1.3 左值引用使用场景和意义 1.4 右值引用的使用场景和意义 1.5 右值引用引用左值及其一些更深入的使用场景分析 1.6 完美转发 1.6.1 万能引用 1.6.2 完美转发
46 0
【C++11】右值引用和移动语义
|
编译器 C++
Dev-c++中将头文件和头文件函数分离,编译主函数跳出undefined reference to 的问题解决
Dev-c++中将头文件和头文件函数分离,编译主函数跳出undefined reference to 的问题解决
Dev-c++中将头文件和头文件函数分离,编译主函数跳出undefined reference to 的问题解决
|
存储 C++
[从C到C++] 1.6 C++引用(Reference)
[TOC] 引用(Reference)是C++语言相对于C语言的又一个扩充,类似于指针,只是在声明的时候用&取代了*。引用可以看做是被引用对象的一个别名,在声明引用时,必须同时对其进行初始化。引用的声明方法如下: 类型标识符 &引用名 = 被引用对象 [例1]C++引用示例: int a = .
2963 0