Object Initialization Syntax in C++ ( T obj = {...} vs T obj{...} )

219 Views Asked by At

What is the difference between the two forms of initialization, T obj = {…} and T obj{…}?
I initially thought T obj = {…} was shorthand for T obj = T{…} where a temporary object is copied into our new object. This, although doesn't execute a copy constructor (copy elision), requires its existence and access to it. But when I blocked copy constructor access in this particular class by making the constructor private, there was no error.
This means that there is no copy mechanism involved. So what's the function of the '=' symbol?
I have referred to the following question but was dissatisfied because of the absence of an explanation:
Is C++11 Uniform Initialization a replacement for the old style syntax?

EDIT: On a similar note, is there a difference between int arr[]{…} and int arr[] = {…}? I am asking this to see if I can bring out the contrast between uniform initialization and list initialization.

2

There are 2 best solutions below

0
On BEST ANSWER

These have almost exactly the same effect:

  • T x = { 1, 2, 3 };
  • T x { 1, 2, 3 };

Technically the version with = is called copy-list-initialization and the other version is direct-list-initialization but the behaviour of both of those forms is specified by the list-initialization behaviour.

The differences are:

  • If copy-list-initialization selects an explicit constructor then the code is ill-formed.
  • If T is auto, then:
    • copy-list-initialization deduces std::initializer_list<Type_of_element>
    • direct-list-initialization only allows a single element in the list, and deduces Type_of_element.

More information: Why does the standard differentiate between direct-list-initialization and copy-list-initialization?


If T is an array type then the above still applies; since array list initialization is always aggregate initialization, there is never a constructor selected and so the two versions are the same in all cases.


T obj = T{...} (excluding auto) is exactly the same as T obj{...}, since C++17, i.e. direct-list-initialization of obj. Prior to C++17 there was direct-list-initialization of a temporary, and then copy-initialization of obj from the temporary.

2
On

I think that comparing these two syntaxes is not your real question.

It seems to me that you are expecting C++17 elision to behave as did the pre-C++17 "optimisation" permitted by the standard and performed by many implementations.

In that "optimisation", though a copy constructor invocation could be elided, it had to be valid and accessible.

That is not the case with C++17 elision.

This is a true elision, whereby just writing T{} doesn't really create a T, but instead says "I want a T", and an actual temporary is "materialised" only if/when it needs to be.

Redundant utterances of this fact are effectively collapsed into one, so despite screaming "I want a T! I want a T! I want a T! I want a T!" the child still only gets one T in the end.

So, in C++17, T obj = T{...} is literally equivalent to T obj{...}.

That explains the results you're seeing, and your confusion.


You may read more about this feature on cppreference.com; here's a snippet from the top of the page:

Mandatory elision of copy/move operations

Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible:

  • [..]
  • In the initialization of an object, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the variable type [..]