When do we need to define destructors?
我读到当我们有指针成员和定义基类时,析构函数需要被定义,但我不确定我是否完全理解。我不确定的一件事是,定义一个默认的构造函数是否无用,因为在默认情况下,我们总是被赋予一个默认的构造函数。另外,我不确定是否需要定义默认的构造函数来实现RAII原则(我们是否只需要将资源分配放到构造函数中,而不定义任何析构函数?).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | class A { public: ~Account() { delete [] brandname; delete b; //do we need to define it? }; something(){} =0; //virtual function (reason #1: base class) private: char *brandname; //c-style string, which is a pointer member (reason #2: has a pointer member) B* b; //instance of class B, which is a pointer member (reason #2) vector<B*> vec; //what about this? } class B: public A { public something() { cout <<"nothing" << endl; } //in all other cases we don't need to define the destructor, nor declare it? } |
三的法则和零的法则
处理资源的好方法是使用三规则(由于移动语义,现在是五规则),但最近另一个规则正在接管:零规则。
但您应该真正阅读本文的想法是,资源管理应该留给其他特定的类。
在这方面,标准库提供了一套很好的工具,如:
在代码中,您有许多不同的资源,这是一个很好的例子。
弦如果您注意到
第二个资源似乎是动态分配的
最后一个资源只是一个
要添加所有建议,您将得到:
1 2 3 4 5 6 7 8 | class A { private: std::string brandname; std::unique_ptr b; std::vector vec; public: virtual void something(){} = 0; }; |
既安全又可读。
正如"胡说八道"所指出的,问题太广泛了…所以我会用我所知道的一切来解决…
重新定义析构函数的第一个原因是在三的规则中,这是Scott Meyers有效C++中的项目6的一部分,但不是完全的。三个规则表示,如果您重新定义了析构函数、复制构造函数或复制赋值操作,那么这意味着您应该重写所有三个操作。原因是,如果必须重写自己的版本,那么编译器默认值将不再对其余版本有效。
另一个例子是Scott Meyers在有效C++中指出的例子。
When you try to delete a derived class object through a base class pointer and the base class has a non virtual destructor, the results are undefined.
然后他继续说
If a class does not contain any virtual functions, that is often an indication that it is not meant to be used as a base class. When a class is not intended to be used as a base class, making the destructor virtual is usually a bad idea.
他关于虚拟的析构函数的结论是
The bottom line is that gratuitously declaring all destructors virtual is just as wrong as never declaring them virtual. In fact, many people summarize the situation this way: declare a virtual destructor in a class if and only if that class contains at least one virtual function.
如果不是三种情况的规则,那么可能你的对象中有一个指针成员,或者你在对象中为它分配了内存,那么你需要在析构函数中管理内存,这是他书中的第6项
一定要查看@jeffrey的答案有两件事需要定义一个析构函数:
当对象被销毁时,需要执行一些操作,而不是销毁所有类成员。
这些操作中的绝大多数曾经是释放内存,根据raii原则,这些操作已经转移到raii容器的析构函数中,编译器负责调用这些容器。但是这些操作可以是任何操作,比如关闭一个文件,或者将一些数据写入日志,或者……如果您严格遵循RAII原则,您将为所有其他操作编写RAII容器,以便只有RAII容器定义了析构函数。
当需要通过基类指针销毁对象时。
当需要这样做时,必须将析构函数定义为基类中的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #include <iostream> class Foo { public: ~Foo() { std::cerr <<"Foo::~Foo() "; }; }; class Bar : public Foo { public: ~Bar() { std::cerr <<"Bar::~Bar() "; }; }; int main() { Foo* bar = new Bar(); delete bar; } |
该程序只打印
如果这两个条件都不满足,则不需要定义析构函数,默认的构造函数就足够了。
下面是您的示例代码:当成员是指向某个对象的指针(作为指针或引用)时,编译器不知道…
…是否有指向此对象的其他指针。
…指针是指向一个对象,还是指向一个数组。
因此,编译器无法推断是否或如何销毁指针指向的内容。所以默认的析构函数永远不会在指针后面销毁任何东西。
这既适用于
这种推理不适用于
如果动态分配内存,并且只希望在对象本身被"终止"时释放该内存,那么需要有一个析构函数。
对象可以通过两种方式"终止":
当使用基类类型的指针显式"终止"时,析构函数必须是
我们知道如果没有提供析构函数,编译器将生成一个。
这意味着除了简单清理之外的任何东西,如基元类型,都需要一个析构函数。
在许多情况下,施工过程中的动态分配或资源获取都有一个清理阶段。例如,可能需要删除动态分配的内存。
如果类表示硬件元素,则可能需要关闭该元素,或将其置于安全状态。
容器可能需要删除其所有元素。
总之,如果类获取资源或需要专门的清理(比如按确定的顺序),那么应该有析构函数。