06_析构函数

析构函数作用:

  1. 析构函数释放对象所用的资源,并销毁对象的非static数据成员。

析构函数使用注意

  1. 类中有堆空间变量(如指针)时,析构函数中要实现对应的内存释放操作(delete/delete[])。
  2. 不要显式调用析构函数,对象销毁时自动调用析构函数!
  3. 析构函数不能是删除的成员,也不能限制为private。

为什么不要显式调用析构函数?

 原因1: 为了理解这个问题,我们必须首先弄明白“堆区”和“栈区”的概念。

堆区(heap) —— 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。

栈区(stack)—— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。如果对象被建立在堆上,系统就不会自动调用。

所以,如果我们在析构函数中有清除堆数据的语句,调用两次意味着第二次会试图清理已经被清理过了的,根本不再存在的数据!这是件会导致运行时错误的问题,并且在编译的时候不会告诉你!

原因2:显式调用的时候,析构函数相当于的一个普通的成员函数。

举个例子说明一下:

Fred *p=new Fred();

delete p; // 自动调用p->~Fred

但是如果我们将上一条语句改为:p->~Fred();呢。会出现什么情况呢?

因为显式调用析构函数不会释放Fred对象本身的内存,也就是栈内存,所以不要这么做,记住delete做了2件事情:调用析构函数和回收内存。

编译器隐式调用析构函数,如分配了堆内存,显式调用析构的话引起重复释放堆内存的异常

把一个对象看作占用了部分栈内存,占用了部分堆内存(如果申请了的话),这样便于理解这个问题

系统隐式调用析构函数的时候,会加入释放栈内存的动作(而堆内存则由用户手工的释放)

用户显式调用析构函数的时候,只是单纯执行析构函数内的语句,不会释放栈内存,摧毁对象

很罕见的例外在于使用布局new(ie: placement new)的时候,在delete设置的缓存之前

析构函数使用注意1:

A base class destructor should be either public and virtual, or protected and nonvirtual.

Reason To prevent undefined behavior.

If the destructor is public, then calling code can attempt to destroy a derived class object through a base class pointer, and the result is undefined if the base class’s destructor is non-virtual.

If the destructor is protected, then calling code cannot destroy through a base class pointer and the destructor does not need to be virtual; it does need to be protected, not private, so that derived destructors can invoke it. In general, the writer of a base class does not know the appropriate action to be done upon destruction.

析构函数" =default" 与 "{}"区别?

If your destructor is virtual, then the difference is negligible(忽略不计的), as Howard pointed out. However, if your destructor was non-virtual, it's a completely different story.

Using = default syntax for special member functions (default constructor, copy/move constructors/assignment, destructors etc) means something very different from simply doing {}. With the latter, the function becomes "user-provided". And that changes everything.

This is a trivial class by C++11's definition:

struct Trivial {
  int foo;
};

If you attempt to default construct one, the compiler will generate a default constructor automatically. Same goes for copy/movement and destructing. Because the user did not provide any of these member functions, the C++11 specification considers this a "trivial" class. It therefore legal to do this, like memcpy their contents around to initialize them and so forth.

This:

struct NotTrivial {
  int foo;
  NotTrivial() {}
};

As the name suggests, this is no longer trivial. It has a default constructor that is user-provided. It doesn't matter if it's empty; as far as the rules of C++11 are concerned, this cannot be a trivial type.

This:

struct Trivial2 {
  int foo;
  Trivial2() = default;
};

Again as the name suggests, this is a trivial type. Why? Because you told the compiler to automatically generate the default constructor. The constructor is therefore not "user-provided." And therefore, the type counts as trivial , since it doesn't have a user-provided default constructor.

The = default syntax is mainly there for doing things like copy constructors/assignment, when you add member functions that prevent the creation of such functions. But it also triggers special behavior from the compiler, so it's useful in default constructors/destructors too.

in short:

They are different. By S() {}, S is considered to have a user-provided constructor, while S() = default; not. This makes a difference, for example, as to whether S qualifies as an aggregate type.

参考:

析构函数的浅谈《原创》 - Rollen Holt - 博客园

https://stackoverflow.com/questions/13576055/how-is-default-different-from-for-default-constructor-and-destructor?rq=1

https://stackoverflow.com/questions/23698203/c-default-constructor-syntax?noredirect=1&lq=1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值