考虑一个占用堆资源类对象的拷贝构造和赋值运算符重载函数,当我们用一个临时对象去拷贝构造一个新对象或者赋值给一个已经存在的对象时,会出现一下的问题:如string类
#include <iostream>
#include <cstring>
using namespace std;
class MyString {
private:
char *mptr;
public:
MyString(const char *src = nullptr) {
cout << "MyString(const char*)" << endl;
if (src != nullptr) {
mptr = new char[strlen(src) + 1];
strcpy(mptr, src);
} else {
mptr = new char[1];
*mptr = '\0';
}
}
MyString(const MyString& src) {
cout << "MyString(const MyString&)" << endl;
mptr = new char[strlen(src.mptr) + 1];
strcpy(mptr, src.mptr);
}
MyString& operator=(const MyString& src) {
cout << "operator=(const MyString&)" << endl;
if (this == &src) {
return *this;
}
delete[] mptr;
mptr = new char[strlen(src.mptr) + 1];
strcpy(mptr, src.mptr);
return *this;
}
~MyString() {
cout << "~MyString()" << endl;
delete[] mptr;
mptr = nullptr;
}
const char* c_str() const {
return mptr;
}
};
MyString getString(MyString &s) {
const char* t = s.c_str();
MyString res(t);
return res;
}
int main() {
MyString s1("aaaaaaaaaaaaaaaaaaaaaaaaaaa");
MyString s2;
s2 = getString(s1);
return 0;
}
1.当我们用临时对象去拷贝构造一个新对象时。
MyString res = MyString(“aaaa”);
显然,这样做很不合理,很浪费!!!你临时对象用完就析构了,还不如直接给我新对象用,避免开辟新空间,避免拷贝!!!
到这里就引出了第一个主题,带右值引用的拷贝构造函数。因为临时对象是右值。临时对象用完就要析构的,那就把临时对象占用的资源直接给新对象就好了。这样做一方面避免了在原来拷贝构造函数需要首先申请空间,然后进行拷贝的麻烦。另一方面避免临时对象析构时还有释放堆资源的麻烦,一举两得!!!
MyString(MyString &&src) {
mptr = src.mptr;
src.mptr = nullptr;
}
2.当我们用临时对象去赋值一个已经存在的对象时。这里指的对象都是持有堆资源的对象。
首先,被赋值的对象要释放自己占用的堆资源,然后申请一个和临时对象指向堆资源一摸一样大小的空间,之后将临时对象指向堆空间的内容拷贝到自己的堆空间中。
这里同样存在着上边的问题,我临时对象给你赋值完我就析构了,堆资源也在析构函数中被释放了,但是你被赋值的对象还得申请空间,还得拷贝,你直接用临时对象的那块堆资源不就好了。
问题就处在临时对象赋值完就析构了,与其白白浪费,不如拿来直接使用,有点“偷”的感觉!!!
MyString& opeator=(MyString &&s) {
if (this == &s) {
return *this;
}
delete[] mptr;
mptr = s.mptr;
s.mptr = nullptr;
return *this;
}
结论:
至此,通过一个例子我们总结出了带右值引用的拷贝构造函数和运算符重载函数所带来效率的提升,以及为什么可以这样处理的原因。在实际开发中,当出现一定要用临时对象作为返回值,要用临时来进行赋值时,我们可以为其类实现带右值引用的拷贝构造函数和运算符重载函数,在程序的效率上会得到很大的提升。