66

Imagine this class:

class Entity {
public:
    int x, y;
    
    Entity() : x(0), y(0) { }
    Entity(int x, int y) : x(x), y(y) { }
}

And here are multiple ways of initializing the class with what I think I know:

Entity ent1;                //Uses the default constructor, so x=0 and y=0
Entity ent2();              //Uses the default constructor, so x=0 and y=0 (Not sure)
Entity ent3(1, 2);          //Made constructor, so x=1 and y=2
Entity ent4 = Entity();     //Default constructor, so x=0 and y=0
Entity ent5 = Entity(2, 3); //Made constructor, so x=2 and y=3

I know that's it's possible to make an object on the heap memory, but that's not what I am looking for at this moment.

My question is, what's the difference between these ways of initializing an object?

I'm not sure which one I should use when.

14
  • 19
    Entity ent2(); is a function declaration.
    – tkausl
    Commented Apr 12, 2018 at 16:49
  • 2
    No, this means there exists no Entity at all, ent2 is a function.
    – tkausl
    Commented Apr 12, 2018 at 16:50
  • 5
    No, it means you are declaring a function called ent2 that returns an Entity. Commented Apr 12, 2018 at 16:50
  • 3
    you can use {} in C++11 and later. Look up uniform initialization. Examples you offered are C++87 (and there MVP probably could allow a varable ent2, compilers were not consistent) Commented Apr 12, 2018 at 17:15
  • 2
    @JesperJuhl that's not my "personal" nickname. it's based on Stroustroup's books first issue. Essentially languge he described in 1986-1987 first IS C++87 . Because there was no other description Commented Apr 12, 2018 at 17:27

1 Answer 1

100

The difference in initialization lies not only in form it takes, but also in type of entity which is being initialized. In this case it's a class-type object with a defined default constructor, as well as a constructor with parameters.

Entity ent1;  

The statement above is default initialization which result in a call of default constructor for the class Entity.


Entity ent2();

The declaration above will be treated by compiler as a function prototype if that's possible. Entity would be returned type of a function ent2, which takes no arguments. It's known as a case of most vexing parse (MVP) and its existence led to appearance of misleading "clever dumb rule": "never use parenthesis".


In statement like this a user-defined constructor that matches list of arguments is invoked for ent3 object:

Entity ent3(1, 2);    // calls Entity(int x, int y)

Another case where MVP can strike is something like this:

Entity ent3_1(int(a), int(b));  // It's not what it looks like.

ent3_1 above is not a variable. The statement declares a function with two int parameters. int(a) being same as int a is legacy of C language and declaration syntax there, which ignores "extra" parenthesis.


Entity ent4 = Entity();

ent4 is a proper version of ent2 case until C++11. Default constructor is invoked as part of value initialization. Its form allows to avoid an ambiguity solving principle which makes ent2 and ent3_1 incorrect. Equal sign here is not an assignment, for no operator= call will happen here. It's part of declaration syntax meant to markup the initialization expression.


Entity ent5 = Entity(2, 3);

ent5 is a version of ent3 case. User-defined constructor invoked as part of value initialization.


Your question is tagged as C++11, and C++11 allows uniform initialization syntax:

Entity ent12{};     // This is a legal alternative of ent2 case
Entity ent13{1, 2}; // A call to constructor or member initialization
Entity ent13{ int(a), int(b) }; // Not a function anymore
Entity ent14 = {};              // Not an assignment
Entity ent15 = Entity{2, 3};    // Not an assignment either!

Note that uniform initialization syntax has a caveat. E.g. this line

std::vector<int> v(10); 

declares a vector of 10 elements. But this one

std::vector<int> v{10};

declares a vector initialized with single element of type int with value 10. This happens because std::vector has a constructor with following signature defined:

vector( std::initializer_list<T> init, const Allocator& alloc = Allocator() );

In case that you can't use neither () without triggering MVP nor {} without invoking undesired constructor, the value initialization assignment syntax allows to resolve the issue.

Addendum: Must watch CppCon 2018: Nicolai Josuttis “The Nightmare of Initialization in C++”

11
  • could you please explain more about the difference between () and {} related to how should I get that the program interpret them differently? And is it possible for my own class to have a constructor that does something when called with {} and something else when called with ()? Commented Apr 17, 2020 at 20:34
  • @CătălinaSîrbu The difference between () or {} is supplying parameters vs. uniform initialization. The example how this may lead to different behavior is actually given above but that's completely separate question to ask. You can't do that with arbitrary form of brace content. Defining constructor that takes std::initializer_list<T> is the key to the trick above, because initialization list's elements of type T will be bound to that proxy class by default. Abuse of this may lead to a mess. Commented Apr 25, 2020 at 13:52
  • Nice answer, but I'm missing the point about the = ... forms requiring the existence and accessibility of a copy constructor, even when that copy constructor call is elided. Commented Aug 17, 2020 at 15:00
  • @cmaster-reinstatemonica does it actually? wandbox.org/permlink/CNYt9MBNllYPP1aY Commented Aug 17, 2020 at 17:16
  • 1
    The point is, that, at least in the old standards, it doesn't matter whether the copy can be elided or not, the copy constructor must be present and accessible because the copy is part of the semantics. I.e. the compiler checks that the copy constructor is present and accessible, and only when that test is positive does it proceed to elide the call to the copy constructor. I think they have changed / planned to change this in the newest standards, but up to C++11, I have verified that it's still the case. Commented Aug 18, 2020 at 7:10