C++11新特性之右值引用(&&)、移动语义(move)、完美转换(forward)
1. 右值引用
个人认为右值引用的目的主要是为了是减少内存拷贝,优化性能。
比如下面的代码:
String Fun() { String str = "hello world"; return str; }
str为临时对象,然后调用Stringd的拷贝构造函数,将临时对象的值赋值给String,这种拷贝是完全没有必要的,如果堆内存很大,那么这个拷贝构造的代价会很大,带来了额外的性能损耗。
为了避免链式对象的拷贝构造,我们可以使用右值引用拷贝的方式来实现:
MyString& operator=(MyString&& other) { cout << "MyString& operator=(const MyString&& other)" << endl; if (this != &other) { m_nLen = other.m_nLen; m_pData = other.m_pData; other.m_pData = NULL; } return *this; }
上面的代码只是进行了指针权限的转移,而没有额外的性能消耗。
1.1 使用右值引用实现MyString类
#include "stdio.h" #include <string> #include <iostream> using namespace std; class MyString { public: MyString() :m_pData(NULL), m_nLen(0) { cout << "MyString()" << endl; } MyString(const char *pStr) // 允许隐式转换 { cout << "MyString(const char *pStr)" << endl; m_nLen = strlen(pStr); CopyData(pStr); } MyString(const MyString& other) { cout << "MyString(const MyString& other)" << endl; if (!other.m_pData) { m_nLen = other.m_nLen; DeleteData(); CopyData(other.m_pData); } } MyString& operator=(const MyString& other) { cout << "MyString& operator=(const MyString& other)" << endl; if (this != &other) { m_nLen = other.m_nLen; DeleteData(); CopyData(other.m_pData); } return *this; } MyString(MyString&& other) { cout << "MyString(MyString&& other)" << endl; m_nLen = other.m_nLen; m_pData = other.m_pData; other.m_pData = NULL; } MyString& operator=(MyString&& other) { cout << "MyString& operator=(const MyString&& other)" << endl; if (this != &other) { m_nLen = other.m_nLen; m_pData = other.m_pData; other.m_pData = NULL; } return *this; } ~MyString() { DeleteData(); } private: void CopyData(const char *pData) { if (pData) { m_pData = new char[m_nLen + 1]; memcpy(m_pData, pData, m_nLen); m_pData[m_nLen] = '\0'; } } void DeleteData() { if (m_pData != NULL) { delete[] m_pData; m_pData = NULL; } } private: char *m_pData; size_t m_nLen; }; MyString Fun() { MyString str = "hello world"; return str; } void main() { MyString str1 = "hello"; MyString str2(str1); MyString str3 = Fun(); }
1.2 右值引用总结
C++11中引入了右值引用和移动语义,可以避免无谓的复制,提高了程序的性能,右值引用标记为T&&。
(1)左值和右值是独立于它们的类型,右值引用类型可能是左值也可能是右值
(2)auto&&或函数参数类型的自动推导的T&&是一个未定的引用类型,它可能是左值引用,也可能是右值引用,取决于初始化的值类型
(3)所有的右值引用叠加到右值引用上仍然是一个右值引用,其它引用叠加都为坐值引用,当T&&为模版参数时,输入左值,它会变为左值引用,输入右值则变为具名的右值引用
(4)编译器会将已命名的右值引用视为左值,而将未命名的右值视为右值
2. move语义
我们知道移动语义是通过右值引用来匹配临时值的,那么,普通的左值是否也能借组移动语义来优化性能呢?C++11为了解决这个问题,提供了std::move()方法来将左值转换为右值,从而方便应用移动语义。move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转义,没有内存拷贝。
MyString str1 = "hello";
MyString str2(str1);
MyString str3 = Fun();
MyString str4 = move(str2);
3. forward
forward将左值转换为右值:
MyString str1 = "hello"; MyString str2(str1); MyString str3 = Fun(); MyString str4 = move(str2); MyString str5(forward<MyString>(str3));
4. 综合示例
#include "stdio.h" #include<iostream> #include<vector> using namespace std; class A { public: A() :m_ptr(NULL), m_nSize(0){} A(int *ptr, int nSize) { m_nSize = nSize; m_ptr = new int[nSize]; if (m_ptr) { memcpy(m_ptr, ptr, sizeof(sizeof(int) * nSize)); } } A(const A& other) // 拷贝构造函数实现深拷贝 { m_nSize = other.m_nSize; if (other.m_ptr) { delete[] m_ptr; m_ptr = new int[m_nSize]; memcpy(m_ptr, other.m_ptr, sizeof(sizeof(int)* m_nSize)); } else { m_ptr = NULL; } cout << "A(const int &i)" << endl; } // 右值应用构造函数 A(A &&other) { m_ptr = NULL; m_nSize = other.m_nSize; if (other.m_ptr) { m_ptr = move(other.m_ptr); // 移动语义 other.m_ptr = NULL; } } ~A() { if (m_ptr) { delete[] m_ptr; m_ptr = NULL; } } void deleteptr() { if (m_ptr) { delete[] m_ptr; m_ptr = NULL; } } int *m_ptr; int m_nSize; }; void main() { int arr[] = { 1, 2, 3 }; A a(arr, sizeof(arr)/sizeof(arr[0])); cout << "m_ptr in a Addr: 0x" << a.m_ptr << endl; A b(a); cout << "m_ptr in b Addr: 0x" << b.m_ptr << endl; b.deleteptr(); A c(std::forward<A>(a)); // 完美转换 cout << "m_ptr in c Addr: 0x" << c.m_ptr << endl; c.deleteptr(); vector<int> vect{ 1, 2, 3, 4, 5 }; cout << "before move vect size: " << vect.size() << endl; vector<int> vect1 = move(vect); cout << "after move vect size: " << vect.size() << endl; cout << "new vect1 size: " << vect1.size() << endl; }