C++ 我们什么时候需要定义析构函数?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22491174/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
When do we need to define destructors?
提问by user3435009
I read that destructors need to be defined when we have pointer members and when we define a base class, but I am not sure if I completely understand. One of the things I am not sure about is whether or not defining a default constructor is useless or not, since we are always given a default constructor by default. Also, I am not sure if we need to define default constructor to implement the RAII principle (do we just need to put resource allocation in a constructor and not define any destructor?).
我读到当我们有指针成员和定义基类时需要定义析构函数,但我不确定我是否完全理解。我不确定的一件事是定义默认构造函数是否无用,因为默认情况下我们总是得到一个默认构造函数。另外,我不确定我们是否需要定义默认构造函数来实现 RAII 原则(我们是否只需要将资源分配放在构造函数中而不定义任何析构函数?)。
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?
}
回答by Shoe
The rule of Three and The Rule of Zero
三法则和零法则
The good ol' way of handling resources was with the Rule of Three(now Rule of Five due to move semantic), but recently another rule is taking over: the Rule of Zero.
处理资源的好方法是使用三规则(由于移动语义现在是五规则),但最近另一个规则正在接管:零规则。
The idea, but you should really read the article, is that resource management should be left to other specific classes.
这个想法,但你应该真正阅读这篇文章,是资源管理应该留给其他特定的类。
On this regard the standard library provides a nice set of tools like: std::vector
, std::string
, std::unique_ptr
and std::shared_ptr
, effectively removing the need for custom destructors, move/copy constructors, move/copy assignment and default constructors.
在这方面的标准库提供了一套很好的工具一样:std::vector
,std::string
,std::unique_ptr
和std::shared_ptr
,有效地消除了自定义的析构函数的需要,移动/拷贝构造函数,移动/拷贝赋值和默认构造。
How to apply it to your code
如何将其应用于您的代码
In your code you have a lot of different resources, and this makes for a great example.
在你的代码中你有很多不同的资源,这是一个很好的例子。
The string
字符串
If you notice brandname
is effectively a "dynamic string", the standard library not only saves you from C-style string, but automatically manages the memory of the string with std::string
.
如果您注意到brandname
实际上是一个“动态字符串”,标准库不仅将您从 C 样式字符串中解救出来,而且会自动管理带有std::string
.
The dynamically allocated B
动态分配的 B
The second resource appears to be a dynamically allocated B
. If you are dynamically allocating for other reasons other than "I want an optional member" you should definitely use std::unique_ptr
that will take care of the resource (deallocating when appropriate) automatically. On the other hand, if you want it to be an optional memberyou can use std::optional
instead.
第二个资源似乎是动态分配的B
. 如果您出于“我想要一个可选成员”以外的其他原因动态分配,您绝对应该使用std::unique_ptr
它来自动处理资源(在适当的时候解除分配)。另一方面,如果您希望它成为可选成员,则可以std::optional
改用。
The collection of Bs
B的集合
The last resource is just an array of B
s. That is easily managed with an std::vector
. The standard library allows you to choose from a variety of different containers for your different needs; Just to mention some of them: std::deque
, std::list
and std::array
.
最后一个资源只是一个B
s数组。使用std::vector
. 标准库允许您从各种不同的容器中进行选择,以满足您的不同需求;仅提及其中一些:std::deque
,std::list
和std::array
。
Conclusion
结论
To add all the suggestions up, you would end up with:
把所有的建议加起来,你会得到:
class A {
private:
std::string brandname;
std::unique_ptr<B> b;
std::vector<B> vec;
public:
virtual void something(){} = 0;
};
Which is both safe and readable.
这既安全又可读。
回答by Claudiordgz
As @nonsensickle points out, the questions is too broad... so I'm gonna try to tackle it with everything I know...
正如@nonsensickle 指出的那样,问题太广泛了......所以我将尝试用我所知道的一切来解决它......
The first reason to re define the destructor would be in The Rule of Threewhich is on part the item 6in Scott Meyers Effective C++ but not entirely. The rule of three says that if you re defined the destructor, copy constructor, or copy assignment operations then that means you should rewrite all three of them. The reason is that if you had to rewrite your own version for one, then the compiler defaults will no longer be valid for the rest.
重新定义析构函数的第一个原因是在 Scott Meyers Effective C++中的第6 项的The Rule of Three 中,但并非完全如此。三法则说,如果你重新定义了析构函数、复制构造函数或复制赋值操作,那么这意味着你应该重写所有三个。原因是如果您必须为一个版本重写自己的版本,那么编译器默认值将不再对其余版本有效。
Another example would be the one pointed out by Scott Meyers in Effective C++
另一个例子是Scott Meyers 在 Effective 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.
当您尝试通过基类指针删除派生类对象并且基类具有非虚拟析构函数时,结果是未定义的。
And then he continues
然后他继续
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.
如果一个类不包含任何虚函数,这通常表明它不打算用作基类。当一个类不打算用作基类时,使析构函数成为虚拟的通常是一个坏主意。
His conclusion on destructors for virtual is
他对虚拟析构函数的结论是
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.
最重要的是,无偿地将所有析构函数声明为虚拟与从不将它们声明为虚拟一样错误。事实上,很多人是这样总结这种情况的:当且仅当该类至少包含一个虚函数时,才在类中声明一个虚析构函数。
And if it is not a Rule Of three case, then maybe you have a pointer member inside your object, and maybe you allocated memory to it inside your object, then, you need to manage that memory in the destructor, this is item 6 on his book
如果它不是三种情况的规则,那么也许您的对象内部有一个指针成员,也许您在对象内部为它分配了内存,然后,您需要在析构函数中管理该内存,这是第 6 项他的书
Be sure to check out @Jefffrey's answer on the Rule of Zero
请务必查看@Jeffrey 对零规则的回答
回答by cmaster - reinstate monica
There are precisely two things that necessitate defining a destructor:
正好有两件事需要定义析构函数:
When your object gets destructed, you need to perform some action other than destructing all class members.
The vast majority of these actions once was freeing memory, with the RAII principle, these actions have moved into the destructors of the RAII containers, which the compiler takes care of calling. But these actions can be anything, like closing a file, or writing some data to a log, or ... . If you strictly follow the RAII principle, you will write RAII containers for all these other actions, so that only RAII containers have destructors defined.
When you need to destruct objects through a base class pointer.
When you need to do this, you mustdefine the destructor to be
virtual
within the base class. Otherwise, your derived destructors won't get called, independent of whether they are defined or not, and whether they arevirtual
or not. Here is an example:#include <iostream> class Foo { public: ~Foo() { std::cerr << "Foo::~Foo()\n"; }; }; class Bar : public Foo { public: ~Bar() { std::cerr << "Bar::~Bar()\n"; }; }; int main() { Foo* bar = new Bar(); delete bar; }
This program only prints
Foo::~Foo()
, the destructor ofBar
is not called. There is no warning or error message. Only partially destructed objects, with all the consequences. So make sure you spot this condition yourself when it arises (or make a point to addvirtual ~Foo() = default;
to each and every nonderived class you define.
当您的对象被破坏时,您需要执行一些操作而不是破坏所有类成员。
这些操作中的绝大多数曾经是释放内存,根据 RAII 原则,这些操作已移至 RAII 容器的析构函数中,由编译器负责调用。但是这些操作可以是任何操作,例如关闭文件,或将一些数据写入日志,或...。如果您严格遵循 RAII 原则,您将为所有这些其他操作编写 RAII 容器,以便只有 RAII 容器定义了析构函数。
当您需要通过基类指针销毁对象时。
当您需要这样做时,您必须
virtual
在基类中定义析构函数。否则,您的派生析构函数将不会被调用,这与它们是否已定义以及是否已定义virtual
无关。下面是一个例子:#include <iostream> class Foo { public: ~Foo() { std::cerr << "Foo::~Foo()\n"; }; }; class Bar : public Foo { public: ~Bar() { std::cerr << "Bar::~Bar()\n"; }; }; int main() { Foo* bar = new Bar(); delete bar; }
该程序只打印
Foo::~Foo()
,Bar
不调用的析构函数。没有警告或错误消息。仅部分破坏的对象,以及所有后果。因此,请确保您在出现这种情况时自己发现它(或者指出要添加virtual ~Foo() = default;
到您定义的每个非派生类中。
If none of these two conditions are met, you don't need to define a destructor, the default constructor will suffice.
如果这两个条件都不满足,则不需要定义析构函数,默认构造函数就足够了。
Now to your example code:
When your member is a pointer to something (either as a pointer or a reference), the compiler does not know ...
现在到您的示例代码:
当您的成员是指向某物的指针(作为指针或引用)时,编译器不知道......
... whether there are other pointers to this object.
... whether the pointer points to one object, or to an array.
...是否有其他指向此对象的指针。
...指针是指向一个对象还是指向一个数组。
Hence, the compiler can't deduce whether, or how to destruct whatever the pointer points to. So the default destructor never destructs anything behind a pointer.
因此,编译器无法推断是否或如何破坏指针指向的任何内容。所以默认的析构函数永远不会破坏指针后面的任何东西。
This applies both to brandname
and to b
. Consequently, you need a destructor, because you need to do the deallocation yourself. Alternatively, you can use RAII containers for them (std::string
, and a smart pointer variant).
这适用于brandname
和b
。因此,您需要一个析构函数,因为您需要自己进行释放。或者,您可以为它们使用 RAII 容器(std::string
和智能指针变体)。
This reasoning does not apply to vec
because this variable directly includes a std::vector<>
withinthe objects. Consequently, the compiler knows that vec
must be destructed, which in turn will destruct all its elements (it's a RAII container, after all).
这个推理不适用于vec
因为这个变量直接包含std::vector<>
在对象中。因此,编译器知道vec
必须销毁它,这反过来又会销毁它的所有元素(毕竟它是一个 RAII 容器)。
回答by barak manos
If you dynamically allocate memory, and you want this memory to be deallocated only when the object itself is "terminated", then you need to have a destructor.
如果您动态分配内存,并且您希望仅在对象本身“终止”时才释放此内存,那么您需要有一个析构函数。
The object can be "terminated" in two ways:
对象可以通过两种方式“终止”:
- If it was statically allocated, then it is "terminated" implicitly (by the compiler).
- If it was dynamically allocated, then it is "terminated" explicitly (by calling
delete
).
- 如果它是静态分配的,那么它会被隐式“终止”(由编译器)。
- 如果它是动态分配的,则它会被显式“终止”(通过调用
delete
)。
When "terminated" explicitly using a pointer of a base-class type, the destructor has to be virtual
.
当使用基类类型的指针显式“终止”时,析构函数必须是virtual
。
回答by Thomas Matthews
We know that if a destructor is not provided, the compiler will generate one.
我们知道,如果没有提供析构函数,编译器就会生成一个。
This means that anything beyond simple cleanup, such as primitive types, will require a destructor.
这意味着除了简单的清理之外的任何东西,比如原始类型,都需要一个析构函数。
In many cases, dynamic allocation or resource acquisition during construction, has a clean up phase. For example, dynamically allocated memory may need to be deleted.
在许多情况下,建设期间的动态分配或资源获取,都有一个清理阶段。例如,可能需要删除动态分配的内存。
If the class represents a hardware element, the element may need to be turned off, or placed into a safe state.
如果该类表示硬件元素,则可能需要关闭该元素,或将其置于安全状态。
Containers may need to delete all of their elements.
容器可能需要删除其所有元素。
In summary, if the class acquires resources or requires specialized cleanup (let's say in a determined order), there should be destructor.
总之,如果类获取资源或需要专门的清理(假设按确定的顺序),则应该有析构函数。