Suppose we have a (toy) C++ class such as the following:
class Foo {
public:
Foo();
private:
int t;
};
Since no destructor is defined, a C++ compiler should create one automatically for class Foo. If the destructor does not need to clean up any dynamically allocated memory (that is, we could reasonably rely on the destructor the compiler gives us), will defining an empty destructor, ie.
Foo::~Foo() { }
do the same thing as the compiler-generated one? What about an empty constructor — that is, Foo::Foo() { }?
If there are differences, where do they exist? If not, is one method preferred over the other?
It will do the same thing (nothing, in essence). But it’s not the same as if you didn’t write it. Because writing the destructor will require a working base-class destructor. If the base class destructor is private or if there is any other reason it can’t be invoked, then your program is faulty. Consider this
That is OK, as long as your don’t require to destruct an object of type B (and thus, implicitly of type A) – like if you never call delete on a dynamically created object, or you never create an object of it in the first place. If you do, then the compiler will display an appropriate diagnostic. Now if you provide one explicitly
That one will try to implicitly call the destructor of the base-class, and will cause a diagnostic already at definition time of
~B.There is another difference that centers around the definition of the destructor and implicit calls to member destructors. Consider this smart pointer member
Let’s assume the object of type
Cis created in the definition of A’s constructor in the.cppfile, which also contains the definition of structC. Now, if you use structA, and require destruction of anAobject, the compiler will provide an implicit definition of the destructor, just like in the case above. That destructor will also implicitly call the destructor of the auto_ptr object. And that will delete the pointer it holds, that points to theCobject – without knowing the definition ofC! That appeared in the.cppfile where struct A’s constructor is defined.This actually is a common problem in implementing the pimpl idiom. The solution here is to add a destructor and provide an empty definition of it in the
.cppfile, where the structCis defined. At the time it invokes the destructor of its member, it will then know the definition of structC, and can correctly call its destructor.Note that
boost::shared_ptrdoes not have that problem: It instead requires a complete type when its constructor is invoked in certain ways.Another point where it makes a difference in current C++ is when you want to use
memsetand friends on such an object that has a user declared destructor. Such types are not PODs anymore (plain old data), and these are not allowed to be bit-copied. Note that this restriction isn’t really needed – and the next C++ version has improved the situation on this, so that it allows you to still bit-copy such types, as long as other more important changes are not made.Since you asked for constructors: Well, for these much the same things are true. Note that constructors also contain implicit calls to destructors. On things like auto_ptr, these calls (even if not actually done at runtime – the pure possibility already matters here) will do the same harm as for destructors, and happen when something in the constructor throws – the compiler is then required to call the destructor of the members. This answer makes some use of implicit definition of default constructors.
Also, the same is true for visibility and PODness that i said about the destructor above.
There is one important difference regarding initialization. If you put a user declared constructor, your type does not receive value initialization of members anymore, and it is up to your constructor to do any initialization that’s needed. Example:
In this case, the following is always true
While the following is undefined behavior, because
bwas never initialized (your constructor omitted that). The value may be zero, but may aswell be any other weird value. Trying to read from such an uninitialized object causes undefined behavior.This is also true for using this syntax in
new, likenew A()(note the parentheses at the end – if they are omitted value initialization is not done, and since there is no user declared constructor that could initialize it,awill be left uninitialized).