C++ コピー コンストラクターの原理分析

1. コンストラクター

C++ オブジェクト指向の学習では、デフォルト コンストラクターとカスタム コンストラクターがあるコンストラクターについてよく理解します。次のクラスのフレームワークがあるとします。

1.1 デフォルトのコンストラクター

class Human{
    
    
public:
	void eat();
private:
	//私有变量,必须提供共有get方法才能获取
	string name;
	int age;
};

この時点で印刷するとき、クラスにコンストラクターが定義されていないため、コンパイラは自動的にデフォルトのコンストラクターを呼び出します。このコンストラクターにはパラメーター リストも関数本体もなく、空のコンストラクターですHuman(){}カスタム コンストラクターがない場合、コンパイラーは自動的にコンストラクターを呼び出します。プログラム内でコンストラクターがカスタマイズされると、デフォルトのコンストラクターは時間とともに無効になり、再度呼び出されなくなります。

1.2 カスタムのデフォルトコンストラクター

class Human{
    
    
public:
	Human();
	void eat();
	string getName();
	int getAge();
private:
	//私有变量,必须提供共有get方法才能获取
	string name;
	int age;
};
string Human::getName(){
    
    
	return name;
}
int Human::getAge(){
    
    
	return age;
}
void Human::eat(){
    
    
	cout << "eat" << endl;
}
Human::Human(){
    
    
	name = "zhongguo";
	age = 9000;
}

int main(){
    
    
	Human hu;
	cout << hu.getName() << endl;//zhongguo
	cout << hu.getAge() <<endl;//9000
	return 0;
}

コンストラクタはカスタマイズされていますが、パラメータはありません。これを一般にカスタムデフォルトコンストラクタと呼びます。もちろん、これをカスタムコンストラクタと呼んでも問題はありません。実は、上記の書き方を一つにまとめて、プロトタイプとコンストラクタHuman()の定義をまとめてpublicに置くこともできます。

class Human{
    
    
public:
	Human(){
    
    
		name = "zhongguo";
		age = 9000;
	}
	void eat();
	string getName();
	int getAge();
private:
	//私有变量,必须提供共有get方法才能获取
	string name;
	int age;
};

1.3 パラメータを使用してコンストラクターをカスタマイズする

このメソッドは、コンストラクターを作成する最も一般的な方法です。

class Human{
    
    
public:
	Human(string name,int age);
	void eat();
	string getName();
	int getAge();
private:
	//私有变量,必须提供共有get方法才能获取
	string name;
	int age;
};
string Human::getName(){
    
    
	return name;
}
int Human::getAge(){
    
    
	return age;
}
void Human::eat(){
    
    
	cout << "eat" << endl;
}
Human::Human(string name,int age){
    
    
	this->name = name;
	this->age = age;
}

int main(){
    
    
	Human hu("zhongguo",9000);
	cout << hu.getName() << endl;//zhongguo
	cout << hu.getAge() <<endl;//9000
	return 0;
}

2 番目に、コピー コンストラクターの基本的な使用法

同様に、プログラム中にコピーコンストラクタを定義していない場合、コンパイラはデフォルトのコピーコンストラクタを呼び出しHuman hu("zhongguo",1000000)ますたとえば、次のコード:Human hu2 = huHuman hu3(hu)

#include <iostream>
#include <Windows.h>
#include <string>

using namespace std;

// 定义一个“人类”
class Human {
    
    
public:
	Human(int age, int salary);//自定义的有参构造函数
	Human(const Human&);//自定义的拷贝构造函数

	string getName();
	int getAge();
	int getSalary();

private:
	string name = "zhonguo";
	int age = 28;
	int salary;
};

//自定义有参构造函数,并且参数不全
Human::Human(int age, int salary) {
    
    
	cout << "调用自定义的有参构造函数" << endl;
	this->age = age;      //this是一个特殊的指针,指向这个对象本身
	this->salary = salary;
	name = "无名";
}

//拷贝构造函数都是引用
//执行Human  h2 = h1; 这句时会被调用,man.name 相当于h1.name只是赋值给了h2.name
Human::Human(const Human& man) {
    
    
	cout << "调用自定义的拷贝构造函数" << endl;
	name = man.name;
	age = man.age;
	salary = man.salary;
}


string Human::getName() {
    
    
	return name;
}

int Human::getAge() {
    
    
	return age;
}

int Human::getSalary() {
    
    
	return salary;
}


int main(void) {
    
    
	Human  h1(25, 35000);  // 使用自定义的默认构造函数
	Human  h2 = h1;  // 使用自定义的拷贝构造函数
	//下面h2的三个成员变量的值得打印与h1完全相同
	cout << "==============================" << endl;
	cout << "姓名:" << h2.getName() << endl;
	cout << "年龄: " << h2.getAge() << endl;
	cout << "薪资:" << h2.getSalary() << endl;
	system("pause");
	return 0;
}

上記のコードではコピーコンストラクターをカスタマイズしていますが、実際にはこの場合デフォルトのコピーコンストラクターを使用するのと同じですが、場合によってはデフォルトのコピーコンストラクターを使用するのは危険です。

自動生成されたコンストラクター (合成コピー コンストラクターとも呼ばれる) は「浅いコピー」、つまりビット コピーであるため

浅いコピーの場合、クラスのメンバーがポインタの場合、エラーが発生します。

以下の例を参照してください。

#include <iostream>
#include <Windows.h>
#include <string>
#include <string.h>

using namespace std;

// 定义一个“人类”
class Human {
    
    
public:
	Human(int age, int salary);
	//Human(const Human&);  //不定义拷贝构造函数,编译器会生成“合成的拷贝构造函数”

	string getName();
	int getAge();
	int getSalary();
	void setAddr(const char* newAddr);
	const char* getAddr();

private:
	string name = "Unknown";
	int age = 28;
	int salary;
	//成员变量中直接定义一个指针
	char* addr;
};


Human::Human(int age, int salary) {
    
    
	cout << "调用自定义的有参构造函数" << endl;
	this->age = age;      //this是一个特殊的指针,指向这个对象本身
	this->salary = salary;
	name = "无名";//将name从unknow换成无名

	addr = new char[64];
	//strcpy_s函数是C++中的复制字符串的函数,头文件<string.h>
	strcpy_s(addr, 64, "China");
}

string Human::getName() {
    
    
	return name;
}

int Human::getAge() {
    
    
	return age;
}

int Human::getSalary() {
    
    
	return salary;
}

void Human::setAddr(const char* newAddr) {
    
    
	if (!newAddr) {
    
    
		return;
	}

	strcpy_s(addr, 64, newAddr);
}

const char* Human::getAddr() {
    
    
	return addr;
}
int main(void) {
    
    
	Human  h1(25, 35000);  // 使用自定义的默认构造函数
	Human  h2 = h1;  // 使用自定义的拷贝构造函数

	cout << "h1 addr:" << h1.getAddr() << endl;
	cout << "h2 addr:" << h2.getAddr() << endl;

	h1.setAddr("长沙");

	cout << "h1 addr:" << h1.getAddr() << endl;
	cout << "h2 addr:" << h2.getAddr() << endl;

	system("pause");
	return 0;
}

結果は次のとおりです。

ここに画像の説明を挿入
自動的に生成されたコピー コンストラクターを使用する場合に問題が見つかりましたHuman h2 = h1

クラス内にポインタメンバが存在する場合、それは一緒に固定されるのと等価であり、h1 の変更は h2 の変更を引き起こします。

C++ strcpy_s と strncpy_s の使用方法

解決策: カスタム コピー コンストラクターで、「ディープ コピー」を使用します。

#include <iostream>
#include <Windows.h>
#include <string>
#include <string.h>

using namespace std;

// 定义一个“人类”
class Human {
    
    
public:
	Human(int age, int salary);
	Human(const Human &man);  //如果不定义拷贝构造函数,编译器会生成“合成的拷贝构造函数”

	string getName();
	int getAge();
	int getSalary();
	void setAddr(const char* newAddr);
	const char* getAddr();

private:
	string name = "Unknown";
	int age = 28;
	int salary;
	//成员变量中直接定义一个指针
	char* addr;
};
Human::Human(const Human & man) {
    
    
	cout << "调用了自定义的拷贝构造函数" << endl;
	age = man.age;
	name = man.name;
	salary = man.salary;
	//使用自定义的拷贝构造函数进行深拷贝
	addr = new char[64];
	strcpy_s(addr,64,man.addr);
}


Human::Human(int age, int salary) {
    
    
	cout << "调用自定义的有参构造函数" << endl;
	this->age = age;      //this是一个特殊的指针,指向这个对象本身
	this->salary = salary;
	name = "无名";//将name从unknow换成无名

	addr = new char[64];
	//strcpy_s函数是C++中的复制字符串的函数,头文件<string.h>
	strcpy_s(addr, 64, "China");
}

string Human::getName() {
    
    
	return name;
}

int Human::getAge() {
    
    
	return age;
}

int Human::getSalary() {
    
    
	return salary;
}

const char* Human::getAddr() {
    
    
	return addr;
}
void Human::setAddr(const char* newAddr) {
    
    
	if (!newAddr) {
    
    
		return;
	}
	strcpy_s(addr, 64, newAddr);
}

int main(void) {
    
    
	Human  h1(25, 35000);  // 使用自定义的默认构造函数
	Human  h2 = h1;  // 使用自定义的拷贝构造函数

	cout << "h1 addr:" << h1.getAddr() << endl;
	cout << "h2 addr:" << h2.getAddr() << endl;

	h1.setAddr("长沙");

	cout << "h1 addr:" << h1.getAddr() << endl;
	cout << "h2 addr:" << h2.getAddr() << endl;

	system("pause");
	return 0;
}

ここに画像の説明を挿入
上記の結果から、h2 のアドレスは変更されていないことがわかります。

2.1 浅いコピーと深いコピー(原理と違い)

深いコピーと浅いコピーの違いはコピーコンストラクターに従って分析できます

通常、デフォルトのコピー コンストラクターは、標準のコピー コンストラクターが参照型であるため、元のオブジェクトの参照を新しいオブジェクトに直接コピーする浅いコピーを使用します。このとき、新しいオブジェクトと古いオブジェクトは同じ値を指します。参照は同じです。はい、元のオブジェクトの値を変更すると、新しいオブジェクトの値も変更されます

また、ディープコピーとは、新しいオブジェクトを作成し、元のオブジェクトのさまざまな属性の値をコピーすることですが、このとき、元のオブジェクトが再割り当てされても、新しいオブジェクトの値は変更されません。

上記の例によると、メンバー オブジェクトにポインターがある場合は、ディープ コピーを使用する必要があります。

浅いコピー
ここに画像の説明を挿入
ディープコピー

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/qq_42595610/article/details/131598817