Difference between constructor calls with and with

2020-05-25 04:11发布

问题:

I'm a C++ beginner and would like to understand why

return std::list<int>();

needs parentheses, but

std::list<int> foo;

doesn't need parentheses. What's the difference between these constructor calls?

回答1:

Neither of these are constructor calls.

The first is an explicit type conversion, which creates an object of type std::list<int>.

The second is a variable definition which creates an object of type std::list<int>.

The default-constructor (constructor taking no arguments) is called as part of the creation in both cases.

Although you might see such things talked about as "constructor calls", there's no syntactic construct to explicitly and singularly call a constructor in C++.

The reason one needs parentheses when the other doesn't is because they are two separate language constructs with different syntax rather than two ways to call a constructor.


Note that if you add parentheses to your second example, you actually declare a function rather than defining a variable:

std::list<int> foo; //variable definition
std::list<int> foo(); //function taking no args, returning a std::list<int>

This is commonly known as the most-vexing-parse. C++11 introduced braced-initialization to get around this:

std::list<int> foo{}; //variable definition

The Standardese, for those so inclined

(Quotes from N3337)

"But T() sure looks like a constructor call, why is it not?"

In that context, T() is known as an explicit type conversion with functional notation:

5.2.3 Explicit type conversion (functional notation) [expr.type.conv]

1 [...]

2 The expression T(), where T is a simple-type-specifier or typename-specifier for a non-array complete object type or the (possibly cv-qualified) void type, creates a prvalue of the specified type, which is value-initialized (8.5; no initialization is done for the void() case). [Note: if T is a non-class type that is cv-qualified, the cv-qualifiers are ignored when determining the type of the resulting prvalue (3.10). —end note ]

So this creates a prvalue which is value-initialized.

[dcl.init]/7: To value-initialize an object of type T means:

if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);

— [...]

So this calls the constructor as part of the value-initialization, which is part of an explicit type conversion. As stated above, there is no way to call a constructor directly. The standard says:

[class.ctor]/1: Constructors do not have names. A special declarator syntax is used to declare or define the constructor. The syntax uses:

— an optional decl-specifier-seq in which each decl-specifier is either a function-specifier or constexpr,

— the constructor’s class name, and

— a parameter list

in that order. In such a declaration, optional parentheses around the constructor class name are ignored.

So constructors don't have names and we declare/define them with a syntax exception which the language defines.

"This seems like an academic distinction, does this matter in practice?"

Maybe, maybe not. My opinion is that interpreting syntax like the above as pure constructor calls paints an incorrect picture of what a constructor is. A constructor initializes an object; it doesn't allocate that object's memory, return the initialized object, bind a symbol to that object or anything else which is done by variable definitions and type conversions. Furthermore, it can create confusion like that of the OP, who expected uniform syntax because he thought those two constructs are both constructor calls.

Why use inexact synecdoche when we have formal terms which avoid confusion?



回答2:

look at it this way:
1) you need to create an object
2) you need to return it.

let's say the compiler looks at the expression return Foo; , the compiler thinks "hey! he wants me to return a type! a type is not a thing that I can return! I need a true variable here!"

so you can write something like

Foo temp;
return temp;

or make it shorter - call the default constructor of Foo , then return the Anonymous object I just created. you treat the constructor as a function that produces an object.

does the code return createDefaultFoo(); looks much more reasonable? well, this is what Foo() does, it creates and returns anonymous Foo obejct

in this line :

std::list<int> foo;

the compiler can tell you want an object named foo from the type std::list<int>. so the () are redundand. as answered here, adding the () will make the compiler think you declare a function.



回答3:

Both statements call default constructor.

return std::list<int>();

This is same as:

std::list<int> value;
return value;

Here an object is created (using default constructor) and object is returned.

std::list<int> foo;

Here object foo is created using the default constructor.

Here are other way to do the same in C++11:

std::list<int> foo;
std::list<int> foo1{}; // C++11