C++右值引用和完美转发

  • 何为引用
    • 引用必须是左值
  • 右值引用
    • 完美转发
  • move()
    • 使用move的优点
    • move 左值测试
    • move 右值测试
    • 注意
  • 参考链接

看到有些同学,调用函数的时候总喜欢使用std::move希望避免一些开销,而实际上由于他并不理解什么是右值引用、完美转发,导致这种努力成为了徒劳,反增笑柄。

本文为记录我学习右值引用和完美转发的笔记。

何为引用

C++新增了一种复合类型,也就是引用变量。通过引用,就可以使用该引用名称或变量名称来指向变量。

引用必须是左值

对于对象的引用必须是左值(常量引用除外)
const引用能够绑定到临时对象, 并将临时对象的生命周期由”创建临时对象的完整表达式”提升至”绑定到的const引用超出作用域”。 non-const 引用没有这个功能

const int& a = 101;//对
int& b = 101;//错

右值引用

右值引用可以从字面意思上理解,指的是以引用传递(而非值传递)的方式使用 C++ 右值。用 “&&” 表示。

完美转发

  • 使用forward()再模板可以做到完美转发,减少拷贝
  • 完美转发的好处是函数可以动态的接受函数参数,从而免去了有可能的拷贝
  • 完美转发的函数内部,需要使用forward()来把接受的参数转化为合适的形式传递出去

我们看Chromium提供的例子:

#include <cstdio>
#include <utility>class MyType {public:MyType() {}MyType(MyType&& other) { fprintf(stderr, "move ctor\n"); }MyType(const MyType& other) { fprintf(stderr, "copy ctor\n"); };
};void Store(const MyType& type) {fprintf(stderr, "store (copy)\n");
}void Store(MyType&& type) {fprintf(stderr, "store (move)\n");
}template<typename T>
void ProcessAndStore(T&& var) {// Process// ...// The type of |var| could be an rvalue reference, which means we should pass// an rvalue to Store. However, it could also be an lvalue reference, which// means we should pass an lvalue.// Note that just doing Store(var); will always pass an lvalue and doing// Store(std::move(var)) will always pass an rvalue. Forward does the right// thing by casting to rvalue only if var is an rvalue reference.Store(std::forward<T>(var));
}int main(int argc, char **argv) {MyType type;// In ProcessAndStore: T = MyType&, var = MyType&ProcessAndStore(type);// In ProcessAndStore: T = MyType, var = MyType&&ProcessAndStore(MyType());
}

move()

  • 这个函数从C++11开始也变为STL函数了

  • 移动赋值函数

  • std::move函数可以以非常简单的方式将左值引用转换为右值引用

  • 应用之一是unique_ptr

  • 移动之后括号内的值就不要再用了, move之后本身就会析构, functions that receive rvalues may act destructively on your variable, so using the variable’s contents afterward may result in undefined behaviour. The only valid things you may do after calling std::move() on a variable are:

    • Destroy it
    • Assign to it (ie. replace its contents)
    • 上一点的原因是,rvalue reference的语义是函数可以认为rvalue外面没有再被引用了。所以可以浅拷贝,可以析构,如果你还要用里面的值的话,就会有问题。
      • doing std::move() on an lvalue reference is bad!!! Code producing an lvalue reference expects the object to remain valid. But code receiving an rvalue reference expects to be able to steal from it. This leaves you with a reference pointing to a potentially-invalid object.
    • 一个例外是当函数参数类型是到模板形参的右值引用(“转发引用”或“通用引用”)时,该情况下转而使用 std::forward
#include <utility>
std::vector<std::string> v;
std::string str = "example";
v.push_back(std::move(str)); // str is now valid but unspecified
str.back(); // undefined behavior if size() == 0: back() has a precondition !empty()
str.clear(); // OK, clear() has no preconditions

使用move的优点

move用于移动构造时,可以使移动构造函数成为浅拷贝,由于rvalue出去也没有引用了,所以很安全,性能也比深拷贝要好

#include <cstdio>
#include <cstring>
#include <vector>class MyType {public:MyType() {pointer_ = new int;*pointer_ = 1;memset(array_, 0, sizeof(array_));vector_.push_back(3.14);}MyType(MyType&& other) {fprintf(stderr, "move ctor\n");// Steal the memory, null out |other|.// 因为other会被析构,所以要把它赋值为null,之所以敢这么做,因为传进来的是个右值,外面没有人用了。pointer_ = other.pointer_;other.pointer_ = nullptr;// Copy the contents of the array.memcpy(array_, other.array_, sizeof(array_));// Swap with our (empty) vector.vector_.swap(other.vector_);}~MyType() {delete pointer_;}private:int* pointer_;char array_[42];std::vector<float> vector_;
};void ProcessMyType(MyType type) {}MyType MakeMyType(int a) {if (a % 2) {MyType type;return type;}MyType type;return type;
}int main(int argc, char **argv) {// MakeMyType returns an rvalue MyType.// Both lines below call our move constructor.MyType type = MakeMyType(2);ProcessMyType(MakeMyType(2));
}

move 左值测试

struct testc {int a;
}
void test_func(testc a) {};
void test_func_1(testc &&a) {};
int main() {testc lv;testc &a = lv;  //左值引用,没有构造testc &&b = static_cast<testc&&>(lv);  // 右值引用,没有构造testc c = std::move(lv);  // 把一个右值引用赋值给一个左值,如果有移动构造函数,则调用移动构造函数。没有则调用拷贝构造函数test_func(std::move(lv)); // 这个move仅仅是把调用test_func时候创建形参的过程从拷贝构造变成了移动构造。并没有减多少开销test_func_1(std::move(lv));  // 没有构造函数,test_func_1直接对形参进行右值引用testc c1;c1 = std::move(lv);  // 这两句话更惨,首先构造了c1,调用了构造函数,然后调用了operator=(testc &c)赋值c1 = lv;  // 同上,是否使用move并没有带来额外的开销。testc &d = std::move(lv);testc &&e std::move(lv);  // 右值引用幅值给右值引用,没有构造, lv仍然可以对结构体进行操作testc f = lv;   // 拷贝构造函数,注意这里没有调用operator+
}

move 右值测试

struct testc {int a;testc (testc &&other) {a = c.a;}// 移动构造还有一种写法testc(testc&& other) {// Note that although the type of |other| is an rvalue reference,// |other| itself is an lvalue, since it is a named object. In order// to ensure that the move assignment is used, we have to explicitly// specify std::move(other).*this = std::move(other);}
}
testc rv() {return testc();
}
testc rv(int a) {// This is here to circumvent some compiler optimizations,// to ensure that we will actually call a move constructor.// 这点比较精髓,如果没有这个wrapper函数,可能所有的左值右值的构造都会被编译器优化掉if (a % 2) {testc c;return c;}testc c;return c;
}void receiver(testc &&a) {...
}int main() {testc lv;  // 构造函数lv = rv(2);  // rv里面会调用构造函数,构造一个临时右值,然后移动构造到一个临时变量(第一个临时变量析构),然后这个临时变量再调用operator=复制,然后rv的临时右值析构(第二个临时变量析构)testc a = rv();  // rv里面调用构造函数,然后a就会直接用这个对象,这中间没有任何构造析构拷贝了,原因是因为编译器优化掉了。testc a1 = rv(2); // rv里面调用构造函数,然后移动构造!!!(如果没有定义移动构造函数,那么就用拷贝构造函数)。然后rv(2)出来的临时右值被析构testc &&b = rv(2);  // 同上。rv里面调用构造函数,然后移动构造到一个新的地方,rc里面的析构,b指向的是新拷贝的一块地方  (由此可见,使用一个右值引用之后,实际上是另外开辟了一个空间存储,这时这个变量就变成左值了!)testc &&c = static_cast<testc&&>(rv(1));  // 同上  testc &&d = static_cast<testc>(rv(1));  // 同上  testc &&d1 = std::move(lv);  // 同上,mv创建一个区域,然后lv移动构造过去,持有的是那片空间testc &&e = std::move(rv(2));  // 同上,但是感觉move那个地方还会有一层析构,并且程序结束之后就不会析构了,不要这么做,rv(2)已经是右值了receiver(rv());  // receiver的形参成为rv()里的临时变量,然后形参就退化成右值了!!!receiver(move(lv));  // 这样就没有任何构造析构了
}

注意

  1. move仍然可以作用于拷贝构造函数,只不过优先级较低,只有没有移动构造函数的情况下采用拷贝构造函数,这时候就把上面例子的移动构造函数变成拷贝构造函数
  2. 移动构造函数只能接受non-const右值,如果有了const右值引用,那么构造函数会用回拷贝构造函数
  3. 不要对右值做move(),因为右值已经是右值了

参考链接

  1. C++11改进我们的程序之右值引用
  2. C++11新特性(66)- 用static_cast将左值转换为右值
  3. C++11 function与bind和move与forward区别引用折叠
  4. Lvalue to rvalue reference binding 由一个小问题,引出了模板的类型推断和引用折叠
  5. google style guide 定义只有移动构造函数和完美转发(perfect forwarding)才能使用右值引用
  6. Rvalue references in Chromium鸿篇巨制
  7. forward

C++右值引用和完美转发相关推荐

  1. C++11特性《 右值引用-<完美转发>、lambda表达式》

    1.右值引用 1.1移动语义 如果一个类中涉及到资源管理,用户必须显式提供拷贝构造.赋值运算符重载以及析构函数,否则编译器将 会自动生成一个默认的,如果遇到拷贝对象或者对象之间相互赋值,就会出错,比如 ...

  2. C++11右值引用、完美转发foward、可变模板参数实例

    本文转载自https://blog.csdn.net/jirryzhang/article/details/82960080 #include <iostream> using names ...

  3. linux 无线网卡驱动桥转发,引用和完美转发

    # 右值引用 移动语意 ~~~ 右值引用解决了左值引用无法传递临时对象和常引用传递的对象只读的问题. 右值引用允许传递一个可变的临时对象引用. 移动构造使用移动而非赋值语义完成构造过程, 主要用于解决 ...

  4. C++11特性——右值引用

      前言:C++11是继98/03版本之后的大改版,其中增加了许多新特性,得到广泛的应用.这篇文章就介绍其中的右值引用.希望能够解释明白以下三个问题:   1.什么是右值引用?   2.右值引用有什么 ...

  5. C++11新特性(一)右值引用

    @ 一.C++11简介 在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名称.不过由于TC1主要是对C+ ...

  6. C++右值引用与转移和完美转发

    C++右值引用与转移和完美转发 1.右值引用 1.1右值 lvalue 是 loactor value 的缩写,rvalue 是 read value 的缩写 左值是指存储在内存中.有明确存储地址(可 ...

  7. 移动语义-右值引用-完美转发-万字长文让你一探究竟

    C++ 右值引用 block://6984617523950616580?from=docs_block&id=ce31003ceb5efb1f7a7c0a5fbe6cb60191627a38 ...

  8. 深入理解右值引用,move语义和完美转发

    move语义 最原始的左值和右值定义可以追溯到C语言时代,左值是可以出现在赋值符的左边和右边,然而右值只能出现在赋值符的右边.在C 里,这种方法作为初步判断左值或右值还是可以的,但不只是那么准确了.你 ...

  9. 可变参数模板、右值引用带来的移动语义完美转发、lambda表达式的理解

    可变参数模板 可变参数模板对参数进行了高度泛化,可以表示任意数目.任意类型的参数: 语法为:在class或者typename后面带上省略号. Template<class ... T> v ...

最新文章

  1. npoi导出execl源码,vs2008实现,包括using库
  2. R语言ggplot2可视化分面图(facet,facet_wrap): 不同分面配置不同的数据范围、自定义每个分面的轴数据格式化形式及数据范围
  3. 科研工作者的神器-zotero论文管理工具
  4. Linux 基础学习大考核
  5. 匀光匀色--直方图匹配算法实现与应用
  6. php xml 格式化,php简单处理XML数据的方法示例
  7. linux wait函数头文件_手把手教Linux驱动9-等待队列waitq
  8. 形参和实参是什么?? shim和polyfil是什么意思??
  9. jenkins修改数据存放路径
  10. 区块链开源框架 HyperLedger Fabric 学习思路分享
  11. 【译】成为明星数据科学家的13大技能
  12. 2018-08-31 基于CSS3D视角,实现视差滚动
  13. 欧姆龙rxd指令讲解_欧姆龙PLC指令表
  14. 固定资产管理系统如何解决企业固定资产管理混乱的问题?
  15. WPS永久关闭热点、云服务、初始登陆界面
  16. 深圳以招聘忽悠面试的培训机构
  17. 刑法285.286.287 条
  18. C/C++黑魔法-另类switch
  19. python模拟登录12306_python模拟登录12306缺少cookies
  20. activiti 流程实例与业务关联

热门文章

  1. NEFU 635(二分+枚举)
  2. TIME_WAIT简介
  3. 多线程学习(二)----AfxBeginThread
  4. 【项目介绍】搜索引擎
  5. Redis 持久化策略 : RDB持久化、AOF持久化、混合持久化
  6. 设计模式:状态模式(State)
  7. JAVA线程间协作:Condition
  8. 计算机基础- -认识磁盘
  9. Python中的注释和算数运算符
  10. VVC专利池最新进展:MC-IF正在召集专利拥有者