左值与右值
左值:表达式结束后仍存在的持久对象;
右值:表达式届时后就不再存在的临时对象。包括将亡值和纯右值。
将亡值:T&&函数返回值,std::move返回值,将要被移动的对象等。
纯右值:非引用返回的临时变量,运算表达式产生的临时变量,原始字面量和lambda表达式等
右值引用的作用
让被引用的右值“重获新生”,其生命周期和右值引用类型变量的生命周期一样,不会被马上析构掉。
struct A
{
A() { cout << "construct: " << ++constructCount << endl; }
A(const A& another) { cout << "copyConstruct: " << ++copyConstructCount << endl; }
~A() { cout << "destruct: " << ++destructCount << endl; }
};
A getA()
{
return A();
}
int main()
{
A& a = getA();
A&& b = getA();
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
如果是a的话会调用临时变量到a的拷贝构造函数,随后临时变量被析构;
但是b的话不会调用临时变量到b的拷贝构造函数!!!相当于b直接用临时变量的内存和数据,节约资源!!!
含有堆内存的class,拷贝构造以及拷贝赋值函数,需要深拷贝。而使用右值引用的拷贝构造函数和拷贝赋值函数,可以避免对临时对象的深拷贝,直接使用临时对象已经申请的资源,节省资源和时间。
class MyString
{
private:
char* m_data;
size_t m_len;
void CopyData(const char* s) //深拷贝数据
{
m_data = new char[m_len + 1];
memcpy(m_data, s, m_len);
m_data[m_len] = '\0';
}
public:
MyString() : m_data(nullptr), m_len(0) {}
MyString(const char* s)
{
m_len = strlen(s);
CopyData(s);
}
MyString(const MyString& another) : m_len(another.m_len)
{
CopyData(another.m_data);
}
MyString& operator=(const MyString& another)
{
this->m_len = another.m_len;
CopyData(another.m_data);
return *this;
}
~MyString() { if (m_len) delete[] m_data; }
};
int main()
{
MyString a;
a = MyString("Hello"); // 调用拷贝赋值函数,有对临时对象“Hello”深拷贝
std::vector<MyString> vec;
vec.push_back(MyString("World")); // 调用拷贝构造函数,有对临时对象“World”的深拷贝
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
MyString(“Hello”)和MyString(“World”)都是临时对象,即右值,但程序仍然会调用拷贝构造和拷贝赋值函数,造成没意义的资源申请和释放,而通过设计右值引用的构造函数,则可以避免浪费,尤其是对需要动态申请大量资源的类。
MyString(MyString&& another)
{
m_len = another.m_len;
m_data = another.m_data; //此处是浅拷贝即可
another.m_len = 0;
another.m_data = nullptr;
}
完美转发
首先运行下示程序:
template<typename T>
void PrintT(T& t){
cout << "lvalue" << endl;
}
template<typename T>
void PrintT(T&& t){
cout << "rvalue" << endl;
}
template<typename T>
void TestForward(T&& v){
PrintT(v);
}
void main(){
TestForward(1);
int x = 1;
TestForward(x);
}
运行结果:
x是左值所以很好理解输出结果为lvalue。但1作为右值输出为lvalue是为什么?
因为1是右值,所以未定义的引用类型T&& v被一个右值初始化后变为具名的右值引用,所以在TestForWard函数内调用PrintT(v)时,此时v是具名变量,它是左值,所以被PrintT(T& )所调用,从而结果打印lvalue。(参照之前在右值引用处总结的三、四条)
那么我们想要保证参数按照原来类型转发到其他函数,就上例而言,希望v依然作为右值被转发。这种转发被称为完美转发。
实现就是靠std::forward函数实现:
template<typename T>
void TestForward(T&& v){
//PrintT(v);
PrintT(std::forward<T>(v)); // forward实现完美转发
}