MyString func(const char* p)
{
MyString tmp(p);
tmp.PrintString();
return tmp;
}
int main()
{
MyString s1("newdata");
s1.PrintString();
s1 = func("yhpinghello");
s1.PrintString();
return 0;
}
首先,想上述代码中的yhpinghello这个字符串常量存储在只读数据区(.rdata)。
这是这个代码的调用图和运行结果
可以发现,这对堆区的骚扰很严重,所以可以进行移动构造和移动赋值。
一、移动构造和移动赋值
移动构造的意思就是让本来s1的资源移动给s2,然后将s1的资源置为空。当然现在我们无法真正的把它移动走,所以move真正的目的是把s1从左值变为右值。 如果我们想要真正的把资源移动走,我们还要加上配套的类型设计。
class MyString
{
private:
char* ptr;
public:
MyString(const char* str = nullptr)
{
if (str != nullptr)
{
int len = strlen(str);
ptr = new char[len + 1];
strcpy(ptr, str);
}
else
{
ptr = new char[1];
*ptr = '\0';
}
}
~MyString()
{
if (ptr != nullptr)
{
delete[]ptr;
}
ptr = nullptr;
}
void PrintString()
{
if (ptr != nullptr)
{
cout << ptr << endl;
}
}
MyString(const MyString& st)
{
if (st.ptr != nullptr)
{
int len = strlen(st.ptr);
ptr = new char[len + 1];//圆括号代表开辟一个空间,方括号代表开辟一组连续空间
strcpy(ptr, st.ptr);
}
else
{
ptr = nullptr;
}
}
MyString& operator=(const MyString& it)
{
if (this == &it)
return *this;
delete[]this->ptr;
this->ptr = nullptr;
if (it.ptr != nullptr)
{
int len = strlen(it.ptr);
ptr = new char[len + 1];
strcpy(ptr, it.ptr);
}
return *this;
}
MyString(MyString&& st) :ptr(st.ptr)
{
st.ptr = nullptr;
cout << this << "move creat" << &st << endl;
}
};
int main()
{
MyString s1("newdata");
MyString s2(std::move(s1));
return 0;
}
这样就可以把s1的资源弄到s2中,并把s1置为空,这就叫做移动构造。
移动构造必须要满足一些条件:
首先被转移资源的对象必须是右值,如果不是要用move将它变成右值。
还必须有相应的移动构造函数配合,这样才能进行移动构造。
如果被转移资源的对象具有常性,move无法把它转移成右值。
int main()
{
const MyString s1("newdata");
//MyString s2(std::move(s1));
MyString s2((MyString&&)s1);
return 0;
}
但是可以这样进行强转。
移动赋值也是如此,也要满足必须是右值和有相应的移动赋值函数来解决。
class MyString
{
private:
char* ptr;
public:
MyString(const char* str = nullptr)
{
if (str != nullptr)
{
int len = strlen(str);
ptr = new char[len + 1];
strcpy(ptr, str);
}
else
{
ptr = new char[1];
*ptr = '\0';
}
}
~MyString()
{
if (ptr != nullptr)
{
delete[]ptr;
}
ptr = nullptr;
}
void PrintString()
{
if (ptr != nullptr)
{
cout << ptr << endl;
}
}
MyString(const MyString& st)
{
if (st.ptr != nullptr)
{
int len = strlen(st.ptr);
ptr = new char[len + 1];//圆括号代表开辟一个空间,方括号代表开辟一组连续空间
strcpy(ptr, st.ptr);
}
else
{
ptr = nullptr;
}
}
MyString& operator=(const MyString& it)
{
if (this == &it)
return *this;
delete[]this->ptr;
this->ptr = nullptr;
if (it.ptr != nullptr)
{
int len = strlen(it.ptr);
ptr = new char[len + 1];
strcpy(ptr, it.ptr);
}
return *this;
}
MyString(MyString&& st) :ptr(st.ptr)
{
st.ptr = nullptr;
cout << this << "move creat" << &st << endl;
}
MyString & operator=(MyString &&st)
{
if (this == &st) return *this;
delete[]ptr;
ptr = st.ptr;
st.ptr = nullptr;
cout << this << "operator =move" << &st << endl;
return *this;
};
int main()
{
MyString s1("yhping");
MyString s2("newdata");
s1.PrintString();
s2.PrintString();
s2 = std::move(s1);
s1.PrintString();
s2.PrintString();
return 0;
}
由此可见,s1的资源被移动赋值给了s2,然后s1的资源为空。
我们此时在回到堆区骚扰严重的那个问题上:
MyString func(const char* p)
{
MyString tmp(p);
tmp.Print();
return tmp;
}
int main()
{
MyString s1("newdata");
s1.Print();
s1 = func("yhpinghello");
s1.Print();
return 0;
}
它的过程图是这样的:
这里其他部分都是正常的,着重解释一下return tmp;部分,这部分是重点。
在return tmp的时候,我们会构建一个亡值对象,这里会调用移动构造函数将亡值对象ptr所指的资源指向tmp对象ptr所指资源,然后将tmp对象ptr所指对象资源置为空。这样当函数栈销毁,将tmp对象析构掉的时候并不会影响堆区的数据。
然后这里在赋值过程中调用的也是移动赋值,原理相同。
对比下来,拥有移动构造和移动赋值可以减少对堆区的骚扰。