C++移动语义
移动语义顾名思义是内存数据的移动,实际就是内存数据所有权的转移。
目的:降低拷贝成本。
参数传递
从本例代码来看,右值引用相当于在参数传递的方式上增加了一种传参的形式。
原有的传参形式:
- 传地址:T* C语言用法
- 传引用:T& 常用于修改对象
- 传应用:const T& 常用于只读对象
- 传值: T 常用于小型对象非频繁传值
- 传右值引用: T&& 常用于临时对象的资源交换,复杂对象避免拷贝
下面的例子
下面的例子将几乎常见的左右左值,右值,出现的常见都呈现了一遍。包括std::move的使用。
任何人都可以通过调试执行来观察到底有没有按照预期走到对应的复制控制成员函数里。
参考《C++ Primer 中文版:第5版》,对应的页码也有标注。
// move example
#include <utility> // std::move
#include <iostream> // std::cout
#include <vector> // std::vector
#include <string> // std::string
using namespace std;
//作者:QQ3508551694
//本文参考书籍:《C++ Primer中文版:第5版》,电子工业出版社
class String
{
friend ostream& operator<<(ostream& os, const String& str);
public:
String(void);
String(const char* data, int length);
String(const char* data);
String(const String& from);//1 复制构造
String& operator = (const String& from);//2 赋值操作符
~String();//3 析构函数
String(String&& from);//4 移动构造
String& operator = (String&& from);//5 赋值操作符
protected:
void clear(void);
void copy(const char* data, size_t length);
private:
char* m_data;
size_t m_length;
int m_id;
static int s_i;
};
int String::s_i = 0;
ostream& operator<<(ostream& os, const String& str)
{
for(size_t i = 0; i < str.m_length; ++i)
os << str.m_data[i];
return os;
}
String::String(void) :m_data(nullptr), m_length(0), m_id(++s_i)
{
cout << "String(" << m_id << ")" << endl;
}
String::~String()
{
cout << "~String(" << m_id << ")" << endl; clear();
}
String::String(const char* data, int _length) :m_data(nullptr), m_length(0), m_id(++s_i)
{
cout << "String(const char*,int," << m_id << ")" << endl;
copy(data, _length);
}
String::String(const char* data) : m_data(nullptr), m_length(0), m_id(++s_i)
{
cout << "String(const char*," << m_id << ")" << endl;
copy(data, strlen(data));
}
String::String(const String& from) :m_data(nullptr), m_length(0), m_id(++s_i)
{
cout << "String(" << m_id << ", const String& " << from.m_id << " )" << endl;
if (from.m_data != m_data)
{
copy(from.m_data, from.m_length);
}
}
String & String::operator=(const String & from)
{
cout << "String & String::operator=(const String &," << m_id << ")" << endl;
if (&from != this)
{
copy(from.m_data, from.m_length);
}
return *this;
}
String::String(String&& from) :m_data(nullptr), m_length(0), m_id(++s_i)
{
cout << "String(" << m_id << ", String&& " << from.m_id << " )" << endl;
if (from.m_data != m_data)
{
swap(m_data, from.m_data);
swap(m_length, from.m_length);
}
}
String & String::operator=(String && from)
{
cout << "String & String::operator=(String &&," << m_id << ")" << endl;
if (&from != this)
{
clear();
this->m_data = from.m_data;//接管资源
this->m_length = from.m_length;
from.m_data = nullptr;//处于可释放状态
from.m_length = 0;
}
return *this;
}
void String::clear(void)
{
if (nullptr != m_data)
{
delete[] m_data;
m_data = nullptr;
m_length = 0;
}
}
void String::copy(const char* data, size_t length)
{
clear();
m_data = new char[length];
for (size_t i = 0; i < length; ++i)
{
m_data[i] = data[i];
}
m_length = length;
}
String& GetLeftValue(void)
{
static String s("Static String");
return s;
}
String GetRightValue(void)
{
String s("Static String");
return s;
}
//左值常见的场景:p471
void TestLeftValue(void)
{
auto& leftValue1 = GetLeftValue();//左值表达式:返回左值引用的函数是左值表达式
String left2;
left2 = (left2 = leftValue1);//左值表达式:赋值表达式返回左值
std::vector<String> arrStr(2);
left2 = (arrStr[0]);//左值表达式:下标返回左值
String arr[2];
left2 = (arr[0]);//左值表达式:下标返回左值
auto p = arr;
left2 = (*p);//左值表达式:接引用返回左值
//++left2, --left2;//左值表达式:下标返回左值(如果用户重载该操作符的话)
arrStr.push_back(left2);//左值表达式:左值变量未超出作用域之前变量名返回左值
}
//右值常见的场景:p471
void TestRightValue(void)
{
String&& rightValue = GetRightValue();//右值表达式:返回非引用类型的函数是右值表达式(右值引用赋值给右值引用,延长右值的生命)
const String& leftValue = GetRightValue();//右值表达式:返回非引用类型的函数是右值表达式,右值引用赋值给左值引用(移动构造)
String leftValue1 = GetRightValue();//右值表达式:返回非引用类型的函数是右值表达式(移动构造)
std::vector<String> arrStr;
arrStr.emplace_back(GetRightValue());//右值表达式:返回非引用类型的函数是右值表达式,右值引用赋值给左值引用(移动构造)
String leftValue2;
leftValue2 = GetRightValue();//右值表达式:返回非引用类型的函数是右值表达式,右值赋给左值(移动赋值)
String leftValue3(rightValue);//!!!!左值表达式:右值引用变量是变量表达式,变量都是左值。因为变量只有超出作用域才释放,是左值很合理(复制构造)!!!!!
arrStr.push_back(rightValue);//!!!!左值表达式:右值引用变量是变量表达式,变量都是左值。因为变量只有超出作用域才释放,是左值很合理(复制构造)!!!!!
//value1 + value2 //右值表达式:算术运算符返回右值(如果用户重载的话)
//value1 - value2 //右值表达式:算术运算符返回右值(如果用户重载的话)
//value1 * value2 //右值表达式:算术运算符返回右值(如果用户重载的话)
//value1 / value2 //右值表达式:算术运算符返回右值(如果用户重载的话)
//value1 < value2 //右值表达式:关系运算符返回右值(如果用户重载的话)
//value1 > value2 //右值表达式:关系运算符返回右值(如果用户重载的话)
//value1 <= value2 //右值表达式:关系运算符返回右值(如果用户重载的话)
//value1 >= value2 //右值表达式:关系运算符返回右值(如果用户重载的话)
//value1 == value2 //右值表达式:关系运算符返回右值(如果用户重载的话)
//value1 != value2 //右值表达式:关系运算符返回右值(如果用户重载的话)
//value1++ value1-- //右值表达式:后自增自减运算符返回右值(如果用户重载的话)
}
String GetValue(void)
{
String a;
String b;
b = a;
return a;//返回非引用类型的函数总是返回右值引用
}
String GetMoveValue(void)
{
String a;
String b;
b = a;
return std::move(a);//变量表达式总是一个左值p471, String&& move(String&)p608, move返回右值引用p609, 返回非引用类型的函数返回右值p471,右值引用的右值引用折叠为右值引用p609,所以函数返回右值引用
}
void TestMove(void)
{
String value = GetValue();//移动构造:返回非引用类型的函数是右值表达式
String value1 = GetMoveValue();//移动构造:返回非引用类型的函数是右值表达式
String leftValue;
auto value2 = std::move(leftValue);//String&& move(String&)
}
int main()
{
TestLeftValue();
TestRightValue();
TestMove();
cin.get();
return 0;
}
程序输出:
String(const char*,1)
String(2)
String & String::operator=(const String &,2)
String & String::operator=(const String &,2)
String(3)
String(4)
String & String::operator=(const String &,2)
String(5)
String(6)
String & String::operator=(const String &,2)
String & String::operator=(const String &,2)
String(7, const String& 2 )
String(8, const String& 3 )
String(9, const String& 4 )
~String(3)
~String(4)
~String(6)
~String(5)
~String(8)
~String(9)
~String(7)
~String(2)
String(const char*,10)
String(11, String&& 10 )
~String(10)
String(const char*,12)
String(13, String&& 12 )
~String(12)
String(const char*,14)
String(15, String&& 14 )
~String(14)
String(const char*,16)
String(17, String&& 16 )
~String(16)
String(18, String&& 17 )
~String(17)
String(19)
String(const char*,20)
String(21, String&& 20 )
~String(20)
String & String::operator=(String &&,19)
~String(21)
String(22, const String& 11 )
String(23, const String& 11 )
String(24, const String& 18 )
~String(18)
~String(22)
~String(19)
~String(24)
~String(23)
~String(15)
~String(13)
~String(11)
String(25)
String(26)
String & String::operator=(const String &,26)
String(27, String&& 25 )
~String(26)
~String(25)
String(28)
String(29)
String & String::operator=(const String &,29)
String(30, String&& 28 )
~String(29)
~String(28)
String(31)
String(32, String&& 31 )
~String(32)
~String(31)
~String(30)
~String(27)