浅拷贝与浅赋值
对于含有堆内存的类类型,我们需要提供深拷贝的拷贝构造函数,如果使用默认构造函数,默认重载赋值运算符函数(简称赋值函数),会导致堆内存的重复删除。
class Mystring { char* str; public: Mystring(const char* p = nullptr) { if (p != nullptr) { int n = strlen(p + 1); str = new char[n]; strcpy_s(str, n, p); } else { str = new char[1]; *str = '\0'; } cout << "Create Mystring:" << this << endl; } Mystring(const Mystring& st) { str = st.str; cout << "Copy Create Mystring: " << this << endl; } Mystring& operator =(const Mystring& st) { if (this != &st) { str = st.str; } cout << this << "Mystring &operator=:" << &st << endl; return *this; } ~Mystring() { delete[]str; str = nullptr; cout << "Destroy Mystring" << endl; } }; int main() { Mystring s1("yhping"); Mystring s2(s1); }
若使用旧对象去构造新对象,调用拷贝构造函数,若调用的是浅拷贝,则两个对象指向同一块内存空间,若析构会导致堆内存的重复删除,就会出现非法访问。
深拷贝与深赋值
class Mystring { char* str; public: Mystring(const char* p = nullptr) { if (p != nullptr) { int n = strlen(p)+1; str = new char[n]; strcpy_s(str, n, p); } else { str = new char[1]; *str = '\0'; } cout << "Create Mystring:" << this << endl; } Mystring(const Mystring& st) { int n = strlen(st.str) + 1; str = new char[n]; strcpy_s(str, n, st.str); cout << "Create Copy Mystring:" << this << endl; } Mystring& operator=(const Mystring& st) { if (this != &st) { delete[]str; int n = strlen(st.str)+1; str = new char[n]; strcpy_s(str, n, st.str); } cout << this << "Mystring &operator=:" << &st << endl; return *this; } ~Mystring() { delete[]str; str = nullptr; cout << "Destroy Mystring" << endl; } void Printstring()const { cout << str << endl; } }; Mystring Getstring() { Mystring str("yhphello"); return str; } int main() { Mystring s1("yhping"); s1.Printstring(); s1 = Getstring(); s1.Printstring(); }
上面函数的Getstring函数将返回局部对象str,通过str拷贝一个临时对象作为返回值来用。此时的临时对象就是将亡值对象,str对象在拷贝完成之后就销毁啦,将亡值对象给是
赋值完后也销毁了,如果对象占用很大的堆内存,那么这个拷贝构造的代价会很大。带来了额外的性能损耗。有没有方法避免临时对象的拷贝构造。当然有办法。
移动构造和移动赋值
移动语义是通过右值引用来匹配(将亡值)临时值
class Mystring { char* str; public: Mystring(const char* p = nullptr) { if (p != nullptr) { int n = strlen(p)+1; str = new char[n]; strcpy_s(str, n, p); } else { str = new char[1]; *str = '\0'; } cout << "Create Mystring:" << this << endl; } Mystring(const Mystring& st) { int n = strlen(st.str) + 1; str = new char[n]; strcpy_s(str, n, st.str); cout << "Create Copy Mystring:" << this << endl; } Mystring& operator=(const Mystring& st) { if (this != &st) { delete[]str; int n = strlen(st.str)+1; str = new char[n]; strcpy_s(str, n, st.str); } cout << this << "Mystring &operator=:" << &st << endl; return *this; } Mystring(Mystring&& st) { str = st.str; st.str = nullptr; cout << "Move Copy Create Mystring " << this << endl; } Mystring& operator=(Mystring&& st) { if (this == &st) { return *this; } if (this->str == st.str) { st.str == nullptr; return *this; } delete []str; str = st.str; st.str = nullptr; cout << "Move Mystring&operator=" << this << endl; } ~Mystring() { delete[]str; str = nullptr; cout << "Destroy Mystring" << endl; } void Printstring()const { cout << str << endl; } }; Mystring Getstring() { Mystring str("yhphello"); return str; } int main() { Mystring s1("yhping"); s1.Printstring(); s1 = Getstring(); s1.Printstring(); }
上面的代码中加入了移动构造(Move Construct)和移动赋值,从移动构造和移动赋值函数的实现中可以看到,它的参数是一个右值引用类型的参数Mystring&&。这里的Mystring&&用来根据参数是左值还是右值来建立分支,如果是临时值(将亡值),则会选择移动构造函数,移动构造函数只是将tmp对象资源做了浅拷贝,不需要对其深拷贝,从而避免了额外的拷贝,提高性能。这就是移动语义。右值引用的一个重要目的是支持移动语义。
移动语义的特点:
1.移动语义可以将系统资源(堆或者系统对象等)通过浅拷贝的方式从一个对象转移到另一个对象,这样就能减少对堆区动态内存分配、数据拷贝、以及对堆区动态内存的释放,可以大幅提高C++程序的性能。
2/消除了对临时对象和自身资源(创建和销毁)的维护而造成的性能浪费。