啃书《C++ Primer Plus》 面向对象部分 动态内存管理(下) 动态成员管理

本篇将介绍当对象成员包含动态成员时,需要对其初始化,拷贝,赋值,销毁等过程做出相应的设计。

啃书《C++ Primer Plus》 面向对象部分 动态内存管理(中) 动态对象的创建 重载new和delete

《动态内存管理》内容思维导图如下:
在这里插入图片描述


动态的成员

在对象的成员变量中,也可以使用动态的对象或者变量,这些动态成员为程序的设计提供了便利,却也暗藏风险。

创建与释放

一般来说,动态成员是通过引用或者指针进行使用和管理的。

我们通过下面的程序来说明动态成员的创建和释放
假设现在有类A和类B,A中包含三个成员:一个B类型引用,一个B类型指针,一个B类型对象

using namespace std;
class B
{
public:
B(){cout << "the constructor in class B" << endl;}
~B(){cout << "the destructor in class B" << endl;}
};
class A
{
public:
A():yb(*new B())	//引用必须使用初始化列表进行初始化
,b()				//对象成员可以在初始化列表中指定调用的构造函数,也可以不适用初始化列表,默认调用无参构造函数
{
    cout << "the constructor in class A" << endl;
	pb = new B();	//指针可以不使用初始化列表初始化,也可以不再构造函数中指定指向对象
}
~A()
{
	delete &yb;		//引用对象不会被自动销毁,引用的是动态对象,务必在析构函数中将其销毁
	delete pb;		//使用指针调用的动态对象,务必不要忘记在析构时销毁
	cout << "the destructor in class A" << endl;
}
B b;
B *pb;
B &yb;
};
int main()
{
	A* p = new A();
	delete p;
}

运行程序,结果如下:
在这里插入图片描述
对上面的程序做一个总结,就是:

  • 对于对象成员,它不是动态的,在对象进行构造前进行构造,当对象析构完成后进行析构。
  • 对于引用动态成员,由于引用的特殊性质,必须在构造函数的参数列表中对其进行初始化,且需要在析构函数中对其堆存进行释放。
  • 对于指针动态成员,创建的时机比较随意,不必一定在构造函数中创建,但是一定要注意在析构函数进行释放。(delete关键字支持对空指针的处理,因此析构函数不需要考虑空指针的问题)

有关构造函数以及初始化列表的问题可参考往期博文:

动态成员的管理——C++三法则

既然我们谈到了类成员中的动态内存。那么除了其内存的分配及释放,自然还好包括对其内容的管理。这里的问题主要涉及到这些对象的赋值,拷贝,析构的问题。

这就不得不提一下C++中的3法则了。那么什么是三法则呢?

简单点说,就是如果需要析构函数,那么一定需要拷贝构造函数和赋值函数

三法则中的三指的就是析构函数拷贝构造函数赋值函数这三个难兄难弟,他们都是奔着动态成员去的。析构,拷贝和赋值可是动态成员出bug的重灾区。

有关这三兄弟的内容可参考往期博客:

问题细数下来,包含但不限于一下方面:

  • 在析构函数中,如果动态成员忘记进行释放,则会造成内存泄漏
  • 在拷贝过程中,如果使用默认的浅拷贝,将会导两个对对象的成员同时指向或引用同一个动态内存造成多次释放的问题
  • 在赋值过程中,除了拷贝遇到的问题外,还有引用成员无法进行浅复制的问题,造成编译错误,以及按位复制造成原动态内存泄漏

下面的程序将演示上面的问题

class B{};
/*没有提供析构函数、拷贝构造函数、赋值函数却包含动态成员的类*/
class A
{
public:
A():b(*new B()),p(new B()){}
B& b;
B* p;
}
int main()
{
	A * p1 = new A();
	A * p2 = new A();
	A * p3 = new A(*p1);	//使用默认拷贝构造函数,p1和p3成员指向同一块内存,容易造成多次释放同一内存产生错误
	*p2 = *p1;				//使用默认赋值函数,p2指针成员直接指向p1的动态成员,造成原动态内存泄漏,
							//同时由于引用不能改变指向,该语句报错
	delete p1,p2,p3;		//释放对象内存,并未释放动态成员内存,造成内存泄漏
}

因此,有必要当类使用到了动态成员时给出三个函数的定义:

class B{};
/*没有提供析构函数、拷贝构造函数、赋值函数却包含动态成员的类*/
class A
{
public:
A():b(*new B()),p(new B()){}

~A(){delete p,&b;}//析构函数,释放动态成员

A(const & a):p(new B(*a.p)),b(* new B(a.b)){}	//拷贝构造函数,根据模板为成员分配新的动态对象

A& operator =(const & a)	//赋值函数,指针对象释放原有对象,根据模板分配新的对象;引用对象,调用其赋值函数进行赋值。
{
	if(&a != this)		//检查自赋值
	{
		delete p;
		p = new B(*a.p);
		b = a.b;
	}
	return *this;
}

B& b;
B* p;
}
int main()
{
	A * p1 = new A();
	A * p2 = new A();
	A * p3 = new A(*p1);	//调用拷贝构造函数
	*p2 = *p1;				//调用赋值函数
	delete p1,p2,p3;		//调用析构函数
}

啃书系列往期博客

语言基础部分:

面向对象部分:

猜你喜欢

转载自blog.csdn.net/wayne_lee_lwc/article/details/106139093
今日推荐