The new syntax "= default" in C++11

I don't understand why would I ever do this:

struct S { 
    int a; 
    S(int aa) : a(aa) {} 
    S() = default; 
};

Why not just say:

S() {} // instead of S() = default;

why bring in a new syntax for that?


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.


I have an example that will show the difference:

#include <iostream>

using namespace std;
class A 
{
public:
    int x;
    A(){}
};

class B 
{
public:
    int x;
    B()=default;
};


int main() 
{ 
    int x = 5;
    new(&x)A(); // Call for empty constructor, which does nothing
    cout << x << endl;
    new(&x)B; // Call for default constructor
    cout << x << endl;
    new(&x)B(); // Call for default constructor + Value initialization
    cout << x << endl;
    return 0; 
} 

Output:

5
5
0

As we can see the call for empty A() constructor does not initialize the members, while B() does it.


n2210 provides some reasons:

The management of defaults has several problems:

  • Constructor definitions are coupled; declaring any constructor suppresses the default constructor.
  • The destructor default is inappropriate to polymorphic classes, requiring an explicit definition.
  • Once a default is suppressed, there is no means to resurrect it.
  • Default implementations are often more efficient than manually specified implementations.
  • Non-default implementations are non-trivial, which affects type semantics, e.g. makes a type non-POD.
  • There is no means to prohibit a special member function or global operator without declaring a (non-trivial) substitute.

type::type() = default;
type::type() { x = 3; }

In some cases, the class body can change without requiring a change in member function definition because the default changes with declaration of additional members.

See Rule-of-Three becomes Rule-of-Five with C++11?:

Note that move constructor and move assignment operator won't be generated for a class that explicitly declares any of the other special member functions, that copy constructor and copy assignment operator won't be generated for a class that explicitly declares a move constructor or move assignment operator, and that a class with a explicitly declared destructor and implicitly defined copy constructor or implicitly defined copy assignment operator is considered deprecated