When do we need to define destructors?
This question already has an answer here:
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.
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
.
The dynamically allocated 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 member you can use std::optional
instead.
The collection of Bs
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
.
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.
As @nonsensickle points out, the questions is too broad... so I'm gonna try to tackle it with everything I know...
The first reason to re define the destructor would be in The Rule of Three which is on part the item 6 in 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.
Another example would be the one pointed out by Scott Meyers in 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
Be sure to check out @Jefffrey's answer on the Rule of Zero
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 must define 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 are virtual
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 of Bar
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 add virtual ~Foo() = default;
to each and every nonderived class you define.
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).
This reasoning does not apply to vec
because this variable directly includes a std::vector<>
within the 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).
上一篇: std :: pair和类的析构函数
下一篇: 我们什么时候需要定义析构函数?