Is there an implicit default constructor in C++?

In the book I'm reading at the moment (C++ Without Fear) it says that if you don't declare a default constructor for a class, the compiler supplies one for you, which "zeroes out each data member". I've experimented with this, and I'm not seeing any zeroing -out behaviour. I also can't find anything that mentions this on Google. Is this just an error or a quirk of a specific compiler?


Solution 1:

If you do not define a constructor, the compiler will define a default constructor for you.

Construction

The implementation of this

default constructor is:

  • default construct the base class (if the base class does not have a default constructor, this is a compilation failure)
  • default construct each member variable in the order of declaration. (If a member does not have a default constructor, this is a compilation failure).

Note:
The POD data (int,float,pointer, etc.) do not have an explicit constructor but the default action is to do nothing (in the vane of C++ philosophy; we do not want to pay for something unless we explicitly ask for it).

Copy

If no destructor/copy Constructor/Copy Assignment operator is defined the compiler builds one of those for you (so a class always has a destructor/Copy Constructor/Assignment Operator (unless you cheat and explicitly declare one but don't define it)).
The default implementation is:

Destructor:

  • If user-defined destructor is defined, execute the code provided.
  • Call the destructor of each member in reverse order of declaration
  • Call the destructor of the base class.

Copy Constructor:

  • Call the Base class Copy Constructor.
  • Call the copy constructor for each member variable in the order of declaration.

Copy Assignment Operator:

  • Call the base class assignment operator
  • Call the copy assignment operator of each member variable in the order of declaration.
  • Return a reference to this.

Note Copy Construction/Assignment operator of POD Data is just copying the data (Hence the shallow copy problem associated with RAW pointers).

Move

If no destructor/copy Constructor/Copy Assignment/Move Constructor/Move Assignment operator is defined the compiler builds the move operators for you one of those for you.
The default implementation is:

Implicitly-declared move constructor If no user-defined move constructors are provided for a class type (struct, class, or union), and all of the following is true:

Move Constructor:

  • Call the Base class Copy Constructor.
  • Call the move constructor for each member variable in the order of declaration.

Move Assignment Operator:

  • Call the base class assignment operator
  • Call the move assignment operator of each member variable in the order of declaration.
  • Return a reference to this.

Solution 2:

I think it's worth pointing out that the default constructor will only be created by the compiler if you provide no constructor whatsoever. That means if you only provide one constructor that takes an argument, the compiler will not create the default no-arg constructor for you.

The zeroing-out behavior that your book talks about is probably specific to a particular compiler. I've always assumed that it can vary and that you should explicitly initialize any data members.

Solution 3:

  • Does the compiler automatically generate a default constructor?
  • Does the implicitly generated default constructor perform zero initialization?

If you legalistically parse the language of the 2003 standard, then the answers are yes, and no. However, this isn't the whole story because unlike a user-defined default constructor, an implicitly defined default constructor is not always used when creating an object from scratch -- there are two other scenarios: no construction and member-wise value-initialization.

The "no construction" case is really just a technicality because it is functionally no different than calling the trivial default constructor. The other case is more interesting: member-wise value-initialization is invoked by using "()" [as if explicitly invoking a constructor that has no arguments] and it bypasses what is technically referred to as the default constructor. Instead it recursively performs value-initialization on each data member, and for primitive data types, this ultimately resolves to zero-initialization.

So in effect, the compiler provides two different implicitly defined default constructors. One of which does perform zero initialization of primitive member data and the other of which does not. Here are some examples of how you can invoke each type of constructor:

    MyClass a; // default-construction or no construction
    MyClass b = MyClass(); // member-wise value-initialization

and

    new MyClass; // default-construction or no construction
    new MyClass(); // member-wise value-initialization

Note: If a user-declared default constructor does exist, then member-wise value-initialization simply calls that and stops.


Here's a somewhat detailed breakdown of what the standard says about this...

  • If you don't declare a constructor, the compiler implicitly creates a default constructor [12.1-5]

  • The default constructor does not initialize primitive types [12.1-7]

      MyClass() {} // implicitly defined constructor
    
  • If you initialize an object with "()", this does not directly invoke the default constructor. Instead, it instigates a long sequence of rules called value-initialization [8.5-7]

  • The net effect of value initialization is that the implicitly declared default constructor is never called. Instead, a recursive member-wise value initialization is invoked which will ultimately zero-initialize any primitive members and calls the default constructor on any members which have a user-declared constructor [8.5-5]

  • Value-initialization applies even to primitive types -- they will be zero-initialized. [8.5-5]

      int a = int(); // equivalent to int a = 0;
    

All of this is really moot for most purposes. The writer of a class cannot generally assume that data members will be zeroed out during an implicit initialization sequence -- so any self-managing class should define its own constructor if it has any primitive data members that require initialization.

So when does this matter?

  • There may be circumstances where generic code wants to force initialization of unknown types. Value-initialization provides a way to do this. Just remember that implicit zero-initialization does not occur if the user has provided a constructor.

  • By default, data contained by std::vector is value-initialized. This can prevent memory debuggers from identifying logic errors associated with otherwise uninitialized memory buffers.

      vector::resize( size_type sz, T c=T() ); // default c is "value-initialized"
    
  • Entire arrays of primitives type or "plain-old-data" (POD)-type structures can be zero-initialized by using value-initialization syntax.

      new int[100]();
    

This post has more details about variations between versions of the standard, and it also notes a case where the standard is applied differently in major compilers.

Solution 4:

C++ does generate a default constructor but only if you don't provide one of your own. The standard says nothing about zeroing out data members. By default when you first construct any object, they're undefined.

This might be confusing because most of the C++ primitive types DO have default "constructors" that init them to zero (int(), bool(), double(), long(), etc.), but the compiler doesn't call them to init POD members like it does for object members.

It's worth noting that the STL does use these constructors to default-construct the contents of containers that hold primitive types. You can take a look at this question for more details on how things in STL containers get inited.