The New Syntax "= Default" in C++11

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.

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.

c++ syntax: default and delete modifiers

Special member functions can now be defaulted or deleted.

A deleted member function still takes part in overload resolution, but if it gets chosen, the program is ill-formed and compilation stops with a useful diagnostic. This is The Right Way to write things like non-copyable classes, and the user gets a proper error message.

A defaulted member function "does what it should", e.g. a defaulted default constructor default-initializes all bases and members and has empty body; a defaulted copy constructor copies each base and member object, and a defaulted assignment operator assigns each base and member object. If any of those operations aren't allowed (e.g. you have reference members), then the defaulted member function is defined as deleted.

Note that your first declaration-definition A() = default; makes the constructor A::A() user-declared but not user-defined; this is important for the classification of A, e.g. whether it is POD. (And notice that this is different from struct A { A(); }; A::A() = default; which is user-defined.)

Another nice consequence is the clarification of implicitly generated things: If you don't write certain functions yourself at all (like copy constructors), one gets implicitly declared for you. When the implicitly-declared one is odr-used, it gets implicitly defined as defaulted, and thus if it's not possible (e.g. if the class has non-copyable members), it actually gets implicitly defined as deleted. So that's generally a neat way of propagating things like non-copyability and non-assignability, at least in terms of the language and the consequent diagnostics.

What does default mean after a class' function declaration?

It's a new C++11 feature.

It means that you want to use the compiler-generated version of that function, so you don't need to specify a body.

You can also use = delete to specify that you don't want the compiler to generate that function automatically.

With the introduction of move constructors and move assignment operators, the rules for when automatic versions of constructors, destructors and assignment operators are generated has become quite complex. Using = default and = delete makes things easier as you don't need to remember the rules: you just say what you want to happen.

C++: default constructor with initialized lists

Only one default?

Yes. Try making two default constructors:

      A() = delete;
A(int val = 10) : x(val) {}

And it will immediately result in an error:

error: call of overloaded ‘A()’ is ambiguous

Is the only way to use it (for class A) is:

A() = default;

The definition from cppreference:

A default constructor is a constructor which can be called with no arguments (either defined with an empty parameter list, or with default arguments provided for every parameter).

So, the answer is no, you can write a default constructor with multiple default parameters, but it should be callable as A() in any context i.e., you should be able to write A object; with any of the constructors below. All of the following are valid default constructors:

A() = default;
A(int x = 10) {}
A(int x = 10, int y = 10) {}
A(std::initializer_list<T> list = {}) {}

A object; // will work with any of the constructors above

Of course you can use only one of them for a class.

Is there any virtue to such a constructor?

It depends on your needs, your application, your design, whatever. The choice is with you. But personally I wouldn't make a default constructor that takes an initializer_list or multiple default arguments just because it adds to the confusion. One important point to keep in mind here is that, in my opinion, the default constructor should be very light and ideally should just be an empty function call. The reason for this is that you often end up using your class in a container and this could result in a lot of default constructor calls. If your default constructor is doing a lot of things, then you will pay a performance penalty. Consider:

std::vector<A> vec(1000); // results in 1000 calls to default constructor of A

Can I also declare (and not only define) a constructor and make it default?

No. If you don't provide a definition for a constructor or any function, it will result in an error which usually goes like undefined reference to xxx. You can still write =default which is close to what you want.

Also note that following two default constructors are not the same:

A() = default; //1
A() {} //2
  • 1 is a trivial default constructor. You are explicitly telling the compiler to generate a trivial default constructor.
  • 2 is non-trivial user defined constructor.

Should functions declared with `= default` only go in the header file

An explicitly-defaulted function is not necessarily not user-provided

What would be the best practice in this case?

I would recommend, as a rule of thumb, unless you explicitly and wantonly know what you are getting into, to always define explicitly-defaulted functions at their (first) declaration; i.e., placing = default at the (first) declaration, meaning in (your case) the header (specifically, the class definition), as there are subtle but essential differences between the two w.r.t. whether a constructor is considered to be user-provided or not.

From [dcl.fct.def.default]/5 [extract, emphasis mine]:

[...] A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. [...]

Thus:

struct A {
A() = default; // NOT user-provided.
int a;
};


struct B {
B(); // user-provided.
int b;
};

// A user-provided explicitly-defaulted constructor.
B::B() = default;

Whether a constructor is user-provided or not does, in turn, affect the rules for which objects of the type are initialized. Particularly, a class type T, when value-initialized, will first zero-initialize the object if T's default constructor is not user-provided. Thus, this guarantee holds for A above, but not for B, and it can be quite surprising that a value-initialization of an object with a (user-provided!) defaulted constructor leaves data members of the object in an uninitialized state.

Quoting from cppreference [extract, emphasis mine]:

Value initialization

Value initialization is performed in these situations:

  • [...]
  • (4) when a named variable (automatic, static, or thread-local) is declared with the initializer consisting of a pair of braces.

The effects of value initialization are:

  • (1) if T is a class type with no default constructor or with a user-provided or deleted default constructor, the object is default-initialized;

  • (2) if T is a class type with a default constructor that is neither user-provided nor deleted (that is, it may be a class with an implicitly-defined or defaulted default constructor), the object is zero-initialized and then it is default-initialized if it has a non-trivial default constructor;

  • ...

Let's apply this on the class types A and B above:

A a{};
// Empty brace direct-list-init:
// -> A has no user-provided constructor
// -> aggregate initialization
// -> data member 'a' is value-initialized
// -> data member 'a' is zero-initialized

B b{};
// Empty brace direct-list-init:
// -> B has a user-provided constructor
// -> value-initialization
// -> default-initialization
// -> the explicitly-defaulted constructor will
// not initialize the data member 'b'
// -> data member 'b' is left in an unititialized state

a.a = b.b; // reading uninitialized b.b: UB!

Thus, even for use cases where you will not end up shooting yourself in the foot, just the presence of a pattern in your code base where explicitly defaulted (special member) functions are not being defined at their (first) declarations may lead to other developers, unknowingly of the subtleties of this pattern, blindly following it and subsequently shooting themselves in their feet instead.



Related Topics



Leave a reply



Submit