C++函数副本机制研究&函数返回值与拷贝构造的浅拷贝和深拷贝的关系

时间:2021-08-16 19:51:47

 C++函数的副本机制到底是怎么一回事呢?我们可以先从函数返回是Void类型,指针类型,引用类型逐渐过度到返回值是类型对象引起的副本机制


首先通过函数返回值是Void类型的研究

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>

using namespace std;

class MyString{

public:
char* pstr;
int length;

MyString():pstr(nullptr),length(0){

}

MyString(char* str){
this->length = strlen(str) + 1;
this->pstr = new char[this->length]{0};
strcpy(this->pstr, str);
}


//返回类型是void的 operator +重载
void operator +(const MyString& str){

this->length = this->length + str.length - 1;
char* temp = new char[this->length]{0};
strcpy(temp, this->pstr);
strcat(temp, str.pstr);

//释放之前的pstr
delete[] this->pstr;
this->pstr = temp;

}


};

void main(){

MyString str1("Nanjing");
MyString str2("Beijing");
//str1.operator+(str2);

//typeid只获取类型 不会执行
cout << typeid(str1 + str2).name() << endl;
//str1 + str2;
str1.operator+(str2);
cout << str1.pstr << str1.length << endl;

cin.get();
}
C++函数副本机制研究&函数返回值与拷贝构造的浅拷贝和深拷贝的关系


二:函数返回值是指针的情况,主要是缺点个了内存分配到栈上自动回收与分配到堆上的情况

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>

using namespace std;

class MyString{

public:
char* pstr;
int length;

MyString():pstr(nullptr),length(0){

}

MyString(char* str){
this->length = strlen(str) + 1;
this->pstr = new char[this->length]{0};
strcpy(this->pstr, str);
}

////错误的情况
//MyString* operator +(const MyString& str){
////栈上 自动回收
//MyString temp;
//temp.length = this->length + str.length - 1;
//temp.pstr = new char[temp.length]{0};
//strcpy(temp.pstr, this->pstr);
//strcat(temp.pstr,str.pstr);
//return &temp;
//}

//正确的情况
MyString* operator +(const MyString& str){
//内存分配在堆上
MyString* temp = new MyString;
temp->length = this->length + str.length - 1;
temp->pstr = new char[temp->length]{0};
strcpy(temp->pstr, this->pstr);
strcat(temp->pstr,str.pstr);
return temp;
}


~MyString(){
delete[] pstr;
}
};

void main(){

MyString str1("Nanjing");
MyString str2("Beijing");
MyString* tstr = str1 + str2;
cout << typeid(str1 + str2).name() << endl;
cout << tstr->pstr << "---" << tstr->length << endl;


cin.get();
}

函数返回值是引用与函数返回值是指针同样的情况,栈上内存自动回收导致垃圾值的出现

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>

using namespace std;

class MyString{

public:
char* pstr;
int length;

MyString():pstr(nullptr),length(0){

}

MyString(char* str){
this->length = strlen(str) + 1;
this->pstr = new char[this->length]{0};
strcpy(this->pstr, str);
}

////错误的情况
//MyString& operator +(const MyString& str){
////栈上 自动回收
//MyString temp;
//temp.length = this->length + str.length - 1;
//temp.pstr = new char[temp.length]{0};
//strcpy(temp.pstr, this->pstr);
//strcat(temp.pstr,str.pstr);
//return temp;
//}

//正确的情况
MyString& operator +(const MyString& str){
//内存分配在堆上 引用本质就是指针 内存的地址
MyString* temp = new MyString;
temp->length = this->length + str.length - 1;
temp->pstr = new char[temp->length]{0};
strcpy(temp->pstr, this->pstr);
strcat(temp->pstr,str.pstr);
return *temp;
}


~MyString(){
delete[] pstr;
}
};

void main(){

MyString str1("Nanjing");
MyString str2("Beijing");
MyString& tstr = str1 + str2;
cout << typeid(str1 + str2).name() << endl;
cout << tstr.pstr << "---" << tstr.length << endl;
cout << (str1 + str2).pstr << "---" << (str1 + str2).length << endl;


cin.get();
}


最终我们看返回值是类型对象的情况会调用类的拷贝构造,这时候拷贝构造函数的浅拷贝与深拷贝有着不一样的情况

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>

using namespace std;

class MyString{

public:
char* pstr;
int length;

MyString():pstr(nullptr),length(0){

}

MyString(char* str){
this->length = strlen(str) + 1;
this->pstr = new char[this->length]{0};
strcpy(this->pstr, str);
}

////拷贝构造 --->浅拷贝的情况
//MyString(const MyString& str){
//this->length = str.length;
//this->pstr = str.pstr;
//}

//拷贝构造 --->深拷贝的情况
MyString(const MyString& str){
cout << "拷贝构造" << endl;
this->length = str.length;
this->pstr = new char[this->length]{0};
strcpy(this->pstr, str.pstr);
}


MyString operator +(const MyString& str){
//返回类型对象的时候 函数有副本机制
/*
* temp这个对象会自动销毁 返回值对象的时候有副本机制 副本机制就会调用拷贝构造
*/
cout << "加法重载" << endl;
MyString temp;
temp.length = this->length + str.length - 1;
temp.pstr = new char[temp.length]{0};
strcpy(temp.pstr, this->pstr);
strcat(temp.pstr,str.pstr);
return temp;
}

void operator = (const MyString& str){
cout << "赋值重载" << endl;
this->length = str.length;
this->pstr = new char[this->length]{0};
strcpy(this->pstr, str.pstr);

}



~MyString(){
delete[] pstr;
}
};

void main(){

MyString str1("Nanjing");
MyString str2("Beijing");

cout << typeid(str1 + str2).name() << endl;
//(str1 + str2); //返回值是一个对象
//cout << (str1 + str2).pstr << "----" << (str1 + str2).length << endl;
MyString str3 = str1 + str2; //
cout << str3.pstr << endl; //编译器优化 只有1次拷贝构造

cout << "==========================" << endl;

str3 = str1 + str2;

cin.get();
}

函数体里面的temp对象因为分配在栈区会自动销毁但是会在销毁之前调用拷贝构造完成一次副本机制 

可以看这张图来理解一下

C++函数副本机制研究&函数返回值与拷贝构造的浅拷贝和深拷贝的关系

如果拷贝构造里面是浅拷贝的话,temp对象销毁调用析构函数,pstr指向的内存也就销毁掉,这时候拷贝构生成的临时对象的pstr就会指向被销毁的内存区域导致错误

如果拷贝构造里面是深拷贝的话,temp对象销毁调用析构函数,pstr指向的内存也就销毁掉,这时候拷贝构生成的临时对象的pstr指向的是自己的内存区域,不会报错

MyString str3 = str1 + str2;  这句话按道理会执行operator+-->拷贝构造--->拷贝构造 但是编译器会进行优化变成MyString str3(str1 + str2)只有一次拷贝构造