首先推荐好书:
背景及问题
如下程序所示:#include<iostream>
class MyString {
public:
MyString() = default;
MyString(const char* data)
{
printf("%s", "MyString Constructed!!\n");
size = strlen(data);
m_data = new char[size];
memcpy(m_data, data, size);
}
~MyString()
{
if (m_data)
{
printf("%s", "MyString Destroyed!!\n");
delete m_data;
}
}
//copy constructor
MyString(const MyString& other) noexcept
{
printf("%s", "MyString Copyed!!\n");
size = other.size;
m_data = new char[size];
memcpy(m_data, other.m_data, size);
}
private:
char* m_data;
int size;
};
class Entity
{
public:
//constructor
Entity(const MyString& string):m_string(string) {}
private:
MyString m_string;
};
int main()
{
Entity entity("Hello");
return 0;
}
程序说明
程序定义了一个MyString类,其中构造函数和拷贝构造函数需要对传进来的字符串开辟空间并复制内容,另外一个Entity类含有一个MyString成员,并在初始化时复制传入的MyString对象。主程序Main中以常量字符串构造一个entity示例。
运行程序会发现,MyString构造了一次,拷贝一次,程序结束析构两次,符合运行逻辑。“Hello”字符串先通过构造函数构造一个临时变量MyString,临时变量再通过Entity内的构造函数拷贝构造给成员变量m_string
问题在于临时变量拷贝构造需要重新开辟空间,并且“用完即扔”,为什么不直接将“hello”构造好的MyString直接“移动”到Entity?这样会节省空间,提高效率。用此引出移动构造和std::move()
移动构造与std::move()
若要实现将临时变量移动到Entity,首先MyString要加入移动构造,如下: MyString(MyString&& other) noexcept
{
printf("%s", "MyString Moved!!\n");
size = other.size;
m_data = other.m_data;
//clear origin data
other.size = 0;
other.m_data = nullptr;
}
此为移动构造,接受的是一个右值,构造是直接复制原MyString的size与data,不重新开辟空间做深拷贝。并将原MyString清零。接着对Entity构造时使用std::move通知移动构造函数,如下:
Entity(MyString&& string) :m_string(std::move(string)) {}
需要注意的是
1. Entity右值构造时也可不使用std::move,直接将参数强转为右值类型也可以,std::move相当于通知构造函数以移动构造的方式进行Entity(MyString&& string) :m_string((MyString&&)string) {}
2. 对于形参为Const YourType &类型的既可以接受左值,也可以接受右值。但是使用std::move会编译错误,因为Const值不能被移动,所以Entity构造仍要单独写一个右值构造
3. MyString移动构造时,复制了临时数据的值还要对其清空,因为数据已经被移动,指针没有置空,析构两次会引起Crash问题