关注C++细节——含有本类对象指针的类的构造函数、析构函数、拷贝构造函数、赋值运算符的例子

本例只是对含有本类对象指针的类的构造函数、析构函数、拷贝构造函数、复制运算符使用方法的一个简单示例,以加深对构造函数和拷贝控制成员的理解。


读C++ primer 5th 第13章后加上自己的理解,完整的写了下课后习题的代码。

第一版:

#include <string>
#include <iostream>

using namespace std;


class TreeNode{
private:
	string value;
	TreeNode *left;
	TreeNode *right;

public:
	TreeNode() : value(""), left(nullptr), right(nullptr){}

	~TreeNode(){
		cout << "~TreeNode()" << endl;
		if (left != nullptr){
			delete left; //递归析构左子树
		}
		if (right != nullptr){
			delete right;//递归析构右子树
		}
	}

	TreeNode(const TreeNode &tn) : value(tn.value), left(nullptr), right(nullptr){
		if (tn.left != nullptr){
			left = new TreeNode(*tn.left);//递归复制拷贝左子树 (其实又一次调用了以(*tn.left)作为参数的复制构造函数)
		}

		if (tn.right != nullptr){
			right = new TreeNode(*tn.right);//递归复制拷贝右子树  (其实又一次调用了以(*tn.left)作为参数的复制构造函数)
		}
	}

	TreeNode & operator=(const TreeNode& tn){
		value = tn.value;

		TreeNode * pl, *pr;

		
		//左侧的对象因为要被覆盖,所以记得如果左侧对象中的指针已经保持有对象,要记得释放资源,否则就会内存泄露了
		if (left != nullptr){
			delete left;
			left = nullptr;
		}

		if (right != nullptr){
			delete right;
			right = nullptr;
		}

		if (tn.left != nullptr){
			left = new TreeNode(*tn.left);//递归赋值左子树 (去调用复制构造函数,这样就会去构造新new出来的这个对象的中保存的对象指针left和right,构造完后此new出来的地址赋予left)下同
		}

		if (tn.right != nullptr){
			right = new TreeNode(*tn.right);//递归复制右子树
		}
		return (*this);
	}

	TreeNode *getLeft()const{
		return left;
	}

	TreeNode *getRight()const{
		return right;
	}

	void setLeft(TreeNode * const le){
		left = le;
	}

	void setRight(TreeNode * const ri){
		right = ri;
	}
};
以上这一版已经能完成正常的复制构造,赋值操作以及正常的析构,并且不会造成内存泄露,但是,这里有一个问题就是不能支持自身给自身赋值,因为一旦给自己赋值,就会出现,现将自己的左右子树析构了,然后再用左右子树做参数,这就会出现未定义的行为,虽然多次运行可能都能得到正确结果,但是确实是非常危险的行为。下面是改进版,此版就支持自身赋值。

ps(借用一下C++ primer中的提示:当你编写一个赋值运算符时,一个好的模式是先将右侧运算对象拷贝到一个局部临时对象中,这样销除左侧运算对象的现有成员就是安全的了,一旦左侧运算对象的资源被销毁,就只剩下将数据从临时对象拷贝到左侧运算对象的成员中了。)  说的确实经典。。。。


改进版:

#include <string>
#include <iostream>

using namespace std;


class TreeNode{
private:
	string value;
	TreeNode *left;
	TreeNode *right;

public:
	TreeNode() : value(""), left(nullptr), right(nullptr){}

	~TreeNode(){
		cout << "~TreeNode()" << endl;
		if (left != nullptr){
			delete left; //递归析构左子树
		}
		if (right != nullptr){
			delete right;//递归析构右子树
		}
	}

	TreeNode(const TreeNode &tn) : value(tn.value), left(nullptr), right(nullptr){
		if (tn.left != nullptr){
			left = new TreeNode(*tn.left);//递归复制拷贝左子树 (其实又一次调用了以(*tn.left)作为参数的复制构造函数)
		}

		if (tn.right != nullptr){
			right = new TreeNode(*tn.right);//递归复制拷贝右子树  (其实又一次调用了以(*tn.left)作为参数的复制构造函数)
		}
	}

	TreeNode & operator=(const TreeNode& tn){
		value = tn.value;

		TreeNode * pl, *pr;

		pl = pr = nullptr;

		if (tn.left != nullptr){
			pl = new TreeNode(*tn.left);//递归赋值左子树 (去调用复制构造函数,这样就会去构造新new出来的这个对象的左指针和右指针,构造完后此new出来的地址赋予left)下同
		}

		if (tn.right != nullptr){
			pr = new TreeNode(*tn.right);//递归复制右子树
		}
		
		//左侧的对象因为要被覆盖,所以记得如果左侧对象的指针已经保持有对象要记得释放资源,否则就会内存泄露了
		if (left != nullptr){
			delete left;
			left = nullptr;
		}

		if (right != nullptr){
			delete right;
			right = nullptr;
		}

		//一下将临时对象赋值过来就ok了,这样的操作也很好的支持了自身赋值
		left = pl;
		right = pr;
		return (*this);
	}

	TreeNode *getLeft()const{
		return left;
	}

	TreeNode *getRight()const{
		return right;
	}

	void setLeft(TreeNode * const le){
		left = le;
	}

	void setRight(TreeNode * const ri){
		right = ri;
	}
};


不断回顾,以加深对构造函数和拷贝控制成员的理解。

关注C++细节——含有本类对象指针的类的构造函数、析构函数、拷贝构造函数、赋值运算符的例子,布布扣,bubuko.com

关注C++细节——含有本类对象指针的类的构造函数、析构函数、拷贝构造函数、赋值运算符的例子

上一篇:RocketMQ


下一篇:【RocketMq源码】八、消息的消费