与其长篇大论的讲原理,不如先举几个栗子。
1. 函数传参
struct DATA {
string value1;
string value2;
DATA(string v1, string v2) : value1(v1), value2(v2) {
std::cout << "DATA" << std::endl;
}
DATA(const DATA& c) { std::cout << "copy DATA" << std::endl; }
~DATA() { std::cout << "~DATA" << std::endl; }
};
void print(DATA&& d) { std::cout << d.value1 << ":" << d.value2 << std::endl; }
void print1(DATA d) { std::cout << d.value1 << ":" << d.value2 << std::endl; }
void test1() {
DATA d("5", "function");
print(std::move(d));
print1(d);
}
如上,print传入d的左值比print1少一次拷贝。
但在实际应用中,我们更喜欢下面的写法,即使用const引用,简介明了,同样也减少了不必要的拷贝。
void print2(const DATA& d) { std::cout << d.value1 << ":" << d.value2 << std::endl; }
2. 移动语义
移动语义,其实就是实现类的移动构造函数。
#include <iostream>
using namespace std;
class demo {
public:
demo() : num(new int(0)) { cout << "construct!" << endl; }
//拷贝构造函数
demo(const demo &d) : num(new int(*d.num)) {
cout << "copy construct!" << endl;
}
~demo() { cout << "class destruct!" << endl; }
private:
int *num;
};
demo get_demo() { return demo(); }
int main() {
demo a = get_demo();
return 0;
}
上面的代码,如果不经过优化,会进行两次copy。(编译器默认开启优化)
[root@localhost test-codes]# g++ -std=c++11 -fno-elide-constructors 02con.cpp -o 02con
[root@localhost test-codes]# ./02con
construct!
copy construct!
class destruct!
copy construct!
class destruct!
class destruct!
[root@localhost test-codes]# g++ -std=c++11 02con.cpp -o 02con
[root@localhost test-codes]# ./02con
construct!
class destruct!
通过浅拷贝方式实现移动构造函数
//移动构造函数
demo(demo &&d):num(d.num){
d.num = NULL;
cout<<"move construct!"<<endl;
}
测试
[root@localhost test-codes]# g++ -std=c++11 -fno-elide-constructors 02con.cpp -o 02con
[root@localhost test-codes]# ./02con
construct!
move construct!
class destruct!
move construct!
class destruct!
class destruct!
当类中同时包含拷贝构造函数和移动构造函数时,如果使用临时对象初始化当前类的对象,编译器会优先调用移动构造函数来完成此操作。只有当类中没有合适的移动构造函数时,编译器才会退而求其次,调用拷贝构造函数。
这就是移动语义,似乎看起来没多少东西,但确实大大提高了C++的性能。
3. 完美转发
完美转发,在实际开发当中,可能用的比较少。
所谓完美转发就是指函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变。
template<typename T>
void function1(T t) {
function2(t);
}
如上,t在function1中是左值,那么在function2中还是左值;t在function1中是右值,那么在function2中还是右值。这就是完美转发。
很明显上面这个模板函数,并没有实现完美转发。因为function2中的t肯定是左值。另外如果t不是引用类型,function1调用function2时还会发生一次拷贝。
那么怎么实现完美转发呢?如下,就是这么简单,加&&
void function2(int &t) { cout << "左值" << endl; }
void function2(const int &t) { cout << "右值" << endl; }
template <typename T>
void function1(T &&t) {
function2(t);
}
int main() {
function1(100);
int num = 333;
function1(std::move(num));
function1(num);
return 0;
}
实现一下,额。。。好像结果不太对。还得引入一个新的函数
template <typename T>
void function1(T &&t) {
function2(forward<T>(t));
}
[root@localhost test-codes]# ./02con
右值
右值
左值
好了这下输出对了。
如果之前没有接触过右值引用,那么到这里我想可以先停一停。
一定会有很多疑问,std::move和forward是做什么的?简单说他们可以把左值转换成右值。
上述只是我对右值引用最简单的理解,其他更深入的思考可以参考《Effective Modern C++》