C++11右值引用
概念
本文主要讲述右值引用相关概念,讲到右值,当然是相对于左值而言。简单理解,能够取地址的 “对象” 为左值,相反则为右值,这里的 “对象” 包含变量、立即数、函数返回值、类型等一切我们想要它进行操作的 target。
左值引用
typename T;
T a; //a 即是一个左值,对它的引用就是左值引用:
T& p = a;
右值引用
右值可以细分为纯右值和将亡值()。
如前所述,右值对象不能取地址,如:
int b = 1; //b是左值,1是纯右值
class A;
A c = A(); //c 是左值,A()是右值(将亡值)。 该语句会报错,后续解释原因。
对右值的引用,即为右值引用:
int&& b = 1; //这里b是对右值 1 的引用
A&& c = A(); //这里c是对一个右值的引用
常量左值引用
一个左值引用只可以绑定左值,如:int& b = 1; 是非法的。
一个右值引用只可以绑定右值:int a = 1; int&& d = a; 是非法的。
常量左值引用:const value_type&, 比较特殊,它可以绑定左值或右值。
引用是变量的别名,右值没有地址,其内容不能被修改,因此左值引用不能指向右值。
但 const 左值引用是可以指向右值的,因为const 限制了该引用所指对象不能被修改,这也是为什么要使用const &作为函数参数的原因之一,如std::vector 的 push_back:
void push_back(const value_type& val);
push_back 的参数既可以是左值,也可以是一个右值。
通过右值引用减少拷贝次数
std::move(左值)
调用 std::move(左值) 返回该左值对象为右值
class A;
A a;
A&& b = std::move(a);
移动函数 std::move 将对象 a 转化为右值,其所对应的内存块的所有权转移给了一个右值引用 b,这过程中并没有调用拷贝构造函数。
这里,声明一个测试类 test.h:
#include <iostream>
class test
{
private:
static int count;
public:
test();
test(const test &c);
void print();
~test();
};
类定义 test.cpp
#include "test.h"
test::test()
{
++count;
std::cout << "construct. " << count <<" address:"<< this << std::endl;
}
test::test(const test &c)
{
++count;
std::cout << "copy constructor." << count <<" address:"<< this << std::endl;
}
test::~test()
{
--count;
std::cout << "destruct." << count <<" address:"<< this <<std::endl;
}
void test::print()
{
std::cout << "this is print() in class test." << std::endl;
}
int test::count = 0;
主函数: main.cpp
#include "test.h"
int main()
{
test a;
test b = a;
test&& c = std::move(a);
}
输出结果:
construct. 1 address:0x7ffcef925bb7
address a = 0x7ffcef925bb7
copy constructor.2 address:0x7ffcef925bb6
address b = 0x7ffcef925bb6
address c = 0x7ffcef925bb7
destruct.1 address:0x7ffcef925bb6
destruct.0 address:0x7ffcef925bb7
可以看出,拷贝构造函数只在给 b赋值时被调用一次,右值引用 c 只是接管了a 的所有权。
进一步,添加函数:
test print()
{
cout << "static void print()." << endl;
test T;
cout << "exit static void print()."<< endl;
return T;
}
修改main.cpp
#include <iostream>
#include "test.cpp"
using namespace std;
static test print();
int main()
{
cout << "hello,world." << endl;
test a;
test b = a;
b.print();
test c = print();
return 0;
}
输出:
hello,world.
construct. 1 address:0x7fff20c8efce
copy constructor.2 address:0x7fff20c8efcd
this is print() in class test.
static void print().
construct. 3 address:0x7fff20c8ef9f
exit static void print().
copy constructor.4 address:0x7fff20c8efcf
destruct.3 address:0x7fff20c8ef9f
copy constructor.4 address:0x7fff20c8efcc
destruct.3 address:0x7fff20c8efcf
destruct.2 address:0x7fff20c8efcc
destruct.1 address:0x7fff20c8efcd
destruct.0 address:0x7fff20c8efce
分析:
如前述所分析test b = a; 调用了拷贝构造函数;
static test print() 函数在返回 对象 T 时一共发生了两次拷贝:
1)通过拷贝构造函数,创建临时对象 tmp,析构T;
2)通过拷贝构造函数,创建对象 c, 析构临时对象 tmp;
修改 static test print()为 static test&& print(), 同时修改main.cpp
test&& print()
{
cout << "static void print()." << endl;
test T;
cout << "exit static void print()."<< endl;
return move(T);
}
static test&& print();
int main()
{
cout << "hello,world." << endl;
test a;
test b = a;
b.print();
test&& c = print();
return 0;
}
test&& print()
{
cout << "static void print()." << endl;
test T;
cout << "exit static void print()."<< endl;
return move(T);
输出:
hello,world.
construct. 1 address:0x7fffcf8dbb17
copy constructor.2 address:0x7fffcf8dbb16
this is print() in class test.
static void print().
construct. 3 address:0x7fffcf8dbaef
exit static void print().
destruct.2 address:0x7fffcf8dbaef
destruct.1 address:0x7fffcf8dbb16
destruct.0 address:0x7fffcf8dbb17
由于 std::move()将 T 转化为右值,其所有权被直接赋予右值引用 c,省去了中间的两次拷贝。
注意:
1)此时如果将 main.cpp中的 c 声明为一个左值,将增加一次拷贝,效果等同于:
test c = test();
2)Linux中采用g++编译器编译,为避免编译器自动优化,编译指令需添加参数:
-fno-elide-constructors