C11性能之道:转移和转发
Posted 渔舟唱晚
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C11性能之道:转移和转发相关的知识,希望对你有一定的参考价值。
1、move
C++11中可以将左值强制转换为右值,从而避免对象的拷贝来提升性能。move将对象的状态或者所有权从一个对象转移到另一个对象,没有内存拷贝。深拷贝和move的区别如图:
从图可以看出,深拷贝会有两份内存,而move只有一份,move只是将内存的所有者切换为目标对象,并没有移动任何东西,只是强制将左值转换为右值。
在C++11之前的拷贝构造函数和赋值函数要有如下定义:
//赋值函数 T &T::operator=(const T& rhs) { //销毁内部资源 //复制rhs资源到自身 } //拷贝函数 A foo(); //假如foo是返回一个A类型的函数 A a; a = foo();
a = foo()将有如下动作:
- 销毁a的资源;
- 赋值foo返回的临时对象的资源;
- 销毁临时对象,释放其资源。
其实可以优化的是,将a和临时对象的资源指针做交换,让临时对象去销毁a原来拥有的资源,a拥有临时对象的资源指针。那么赋值操作函数就该这么写:
T &T::operator=(const T& rhs) { //转移资源的控制权,无需复制 }
假如一个临时容器很大,要赋值给另一个容器:
//常规 std::list<std::string> tokens; //很大容量 std::list<std::string> ret = tokens; //move std::list<std::string> tokens; //很大容量 std::list<std::string> ret = std::move(tokens);
如果不用move,拷贝的代价很大,性能较低,使用move几乎没有代价,只转换了资源的所有权,实际上将左值变成右值引用。move对于拥有内存、文件句柄等资源的对象有效,但是对于int、char等类型仍然会产生拷贝。当然,当一个左值被move之后,不再对之前的内存有控制权,会被置为一个空对象。
#include <iostream> using namespace std; int main() { string s = "hello"; cout << "before:" << s << endl; string s2 = move(s); cout << "after:" << s << endl; cout << "s2:" << s2 << endl; return 0; } //运行结果 before:hello after: s2:hello
2、forward完美转发
之前的文章有提到过,当一个右值经过函数内部的转发会将其类型变成一个左值,并不是他原来的类型。
foo(int && i) { foo2(i); } foo(5); //5在此时是右值,在foo2调用的时候就是左值了
那么我们该怎么优化,使参数按照原来的类型来转发呢?C++11提供了std::forward,将参数传递给函数中调用的另一个函数,按照参数本来的类型转发。
具体用法如下:
#include <iostream> #include <utility> using namespace std; void PrintValue(int &i) { cout << "Lvalue:" << i << endl; } void PrintValue(int &&i) { cout << "Rvalue:" << i << endl; } void Forward(int &&i) { cout << "no forward:"; PrintValue(i); //未经转发会变成左值 cout << "forward:"; PrintValue(std::forward<int>(i)); //转发 } int main() { int i = 1; cout << "param Rvalue:" << endl; Forward(2); //cout << "param Lvalue:" << endl; //Forward(i); //不能传入左值 cout << "move Lvalue:" << endl; Forward(move(i)); return 0; }
以上是关于C11性能之道:转移和转发的主要内容,如果未能解决你的问题,请参考以下文章