Is There an Implicit Default Constructor in C++

What is the point of a C++ implicit default constructor?

The point of the implicit default constructor is the same point as of any other constructor.

Something needs to construct every instance of a given class. The class instance is not going to appear out of thin air, by itself. Something, somewhere, has the job of constructing the object.

If an explicit constructor is not declared, an implicit default constructor gets automatically defined, that default-constructs the class's superclasses, and any class members.

Here, "default-constructs" also includes the "do nothing" option, if the class member is a primitive type with no explicit constructor. So, in the end, the implicit default constructor may end up doing nothing. But it is still defined, if an explicit constructor is not specified (and if the implicit default constructor is not explicitly deleted, of course).

Is there an implicit default constructor in C++?

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.

Default vs. Implicit constructor in C++

The terms default and implicit, when talking about a constructor have the following meaning:

  • default constructor is a constructor that can be called with no arguments. It either takes no arguments or has default values for each of the arguments taken.

  • implicit constructor is a term commonly used to talk about two different concepts in the language, the

    • implicitly declared constructor which is a default or copy constructor that will be declared for all user classes if no user defined constructor is provided (default) or no copy constructor is provided (copy). That is, a class with no constructors declared by the user has one default constructor implicitly declared.

    • implicitly defined constructor is a implicitly declared constructor that is used (odr-used1 in the language and for which the compiler will provide a definition.

     

    struct test
    {
    test(int i = 0) { }
    // test(test const&) implicitly declared here
    };

    struct test2 { }; // implicitly declared: test2(), test2(test2 const&)

    int main()
    {
    test t;

    test copy(t); // causes *definition* of the implicitly
    // declared copy constructor

    test2 t2; // causes *definition* of test2::test2()

    test2 copy2(t2); // causes *definition* of test2::test2(test2 const&)
    }

In simple terms, a constructor is default if it can be called with no arguments. A constructor is implicit(ly declared/defined) if it is not provided by the user but declared/defined.

As of the specific cases:

Test t1;

Uses the default constructor, Test(int = 0), which is not implicit.

Test t2();

This is a strange quirk of the language, it declares a function that takes no arguments and returns a Test object.

Test t3 = 3;

This is called copy-initialization and is equivalent to the composition of an implicit* conversion from 3 to Test and copy construction of t3 from the result of the conversion. This will use the Test(int) constructor for the conversion, and then the implicitly defined (and declared) copy constructor. Note: the compiler can optimize away the copy, but it must verify that the copy constructor is available (access specifiers) and can be defined.

Test t4(4);

Uses the Test(int) constructor, which in this case is not acting as a default constructor.

Test t5 = Test(5);

Equivalent to the Test t3 = 3 case, with the only difference that the conversion from 5 to Test is explicit in this case. In this example it won't matter, but if the constructor had been marked as explicit this line would compile while the t3 case would fail.


*) Yet another use of implicit, in this case referring to the fact that the conversion from 3 to Test is not explicitly requested in the code. Compare this with t5 where the conversion is explicitly requested by the programmer: Test(5).

Defaulted constructor vs implicit constructor

The purpose of = default is to make the implicit definition explicit. Any differences between an implicitly defined version and the explicitly defaulted version are limited to some additional possibilities appearing due to the presence of an explicit declaration.

  1. The implicitly declared/defined constructor is always public, whereas the access control of the explicitly defined defaulted constructor is under your own control.

  2. Defining a defaulted default constructor enables you annotating it with attributes. For example:

    $ cat a.cpp 
    class A
    {
    public:
    [[deprecated]] A() = default;
    };

    int main()
    {
    A a;
    }

    $ g++ -std=c++14 a.cpp
    a.cpp: In function ‘int main()’:
    a.cpp:9:7: warning: ‘constexpr A::A()’ is deprecated [-Wdeprecated-declarations]
    A a;
    ^
    a.cpp:4:20: note: declared here
    [[deprecated]] A() = default;
    ^

The new syntax = default in C++11

A defaulted default constructor is specifically defined as being the same as a user-defined default constructor with no initialization list and an empty compound statement.

§12.1/6 [class.ctor] A default constructor that is defaulted and not defined as deleted is implicitly defined when it is odr-used to create an object of its class type or when it is explicitly defaulted after its first declaration. The implicitly-defined default constructor performs the set of initializations of the class that would be performed by a user-written default constructor for that class with no ctor-initializer (12.6.2) and an empty compound-statement. [...]

However, while both constructors will behave the same, providing an empty implementation does affect some properties of the class. Giving a user-defined constructor, even though it does nothing, makes the type not an aggregate and also not trivial. If you want your class to be an aggregate or a trivial type (or by transitivity, a POD type), then you need to use = default.

§8.5.1/1 [dcl.init.aggr] An aggregate is an array or a class with no user-provided constructors, [and...]

§12.1/5 [class.ctor] A default constructor is trivial if it is not user-provided and [...]

§9/6 [class] A trivial class is a class that has a trivial default constructor and [...]

To demonstrate:

#include <type_traits>

struct X {
X() = default;
};

struct Y {
Y() { };
};

int main() {
static_assert(std::is_trivial<X>::value, "X should be trivial");
static_assert(std::is_pod<X>::value, "X should be POD");

static_assert(!std::is_trivial<Y>::value, "Y should not be trivial");
static_assert(!std::is_pod<Y>::value, "Y should not be POD");
}

Additionally, explicitly defaulting a constructor will make it constexpr if the implicit constructor would have been and will also give it the same exception specification that the implicit constructor would have had. In the case you've given, the implicit constructor would not have been constexpr (because it would leave a data member uninitialized) and it would also have an empty exception specification, so there is no difference. But yes, in the general case you could manually specify constexpr and the exception specification to match the implicit constructor.

Using = default does bring some uniformity, because it can also be used with copy/move constructors and destructors. An empty copy constructor, for example, will not do the same as a defaulted copy constructor (which will perform member-wise copy of its members). Using the = default (or = delete) syntax uniformly for each of these special member functions makes your code easier to read by explicitly stating your intent.



Related Topics



Leave a reply



Submit