Will an 'Empty' Constructor or Destructor Do the Same Thing as the Generated One

Will an 'empty' constructor or destructor do the same thing as the generated one?

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

struct A { private: ~A(); };
struct B : A { };

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

struct A { private: ~A(); };
struct B : A { ~B() { /* ... */ } };

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

struct C;
struct A {
auto_ptr<C> a;
A();
};

Let's assume the object of type C is created in the definition of A's constructor in the .cpp file, which also contains the definition of struct C. Now, if you use struct A, and require destruction of an A object, 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 the C object - without knowing the definition of C! That appeared in the .cpp file 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 .cpp file, where the struct C is defined. At the time it invokes the destructor of its member, it will then know the definition of struct C, and can correctly call its destructor.

struct C;
struct A {
auto_ptr<C> a;
A();
~A(); // defined as ~A() { } in .cpp file, too
};

Note that boost::shared_ptr does 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 memset and 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:

struct A {
int a;
};

struct B {
int b;
B() { }
};

In this case, the following is always true

assert(A().a == 0);

While the following is undefined behavior, because b was 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.

assert(B().b == 0);

This is also true for using this syntax in new, like new 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, a will be left uninitialized).

Can an empty destructor cause harm?

The question depends on several parameters. Emptiness isn't the only thing that has effect on the result. E.g., if you don't define virtual destructor (empty or not), you'll get problematic behavior when inheriting from the class. On the other hand, if you define an empty destructor in private or protected section, it will prevent creating instances of the class on stack.

What does empty destructor do?

Your destructor may look empty, but it's actually destructing the member variables. In this case, it's destructing myset, so the subsequent insert(20) is crashing.

If your class had no non-POD member variables then the empty destructor would truly do nothing.

Will destructor delete built-in types and pointer objects?

What the spec means is that no code is run to clean up int i. It simply ceases to exist. Its memory is part of Foo and whenever a Foo instance is released, then i goes with it.

For pointers the same is true, the pointer itself will simply disappear (it's really just another number), but the pointer might point at something that needs to also be released. The compiler doesn't know if that is the case, so you have to write a destructor.

This is why things like std::shared_ptr exist; they are clever pointers (aka 'smart') and the compiler does know if it points at something that needs to be released and will generate the correct code to do so. This is why you should always use smart pointers instead of 'naked' ones (like int *p).

How is =default different from {} for default constructor and destructor?

This is a completely different question when asking about constructors than destructors.

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. The same is true of constructors.

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 c++, if I create a constructor that takes one argument which has a default value - will that serve as a default (empty) constructor?

Yes, the definition of default constructor allows parameters as long as they have default values:

A default constructor for a class X is a constructor of class X for which each parameter that is not a function parameter pack has a default argument (including the case of a constructor with no parameters).

(from the C++1z draft)

Older phrasing:

A default constructor for a class X is a constructor of class X that can be called without an argument.


In addition, your copy constructor will be implicitly defined as defaulted, because you haven't declared one.

There is no such thing as a "default copy constructor". But "default constructor" and "defaulted copy constructor" are meaningful.



Related Topics



Leave a reply



Submit