这些都不是构造函数调用。
第一个是显式类型转换 http://en.cppreference.com/w/cpp/language/explicit_cast,它创建一个类型的对象std::list<int>
.
第二个是变量定义,它创建类型的对象std::list<int>
.
在这两种情况下,默认构造函数(不带参数的构造函数)都会作为创建的一部分被调用。
尽管您可能会看到诸如“构造函数调用”之类的内容,但在 C++ 中没有显式且单独调用构造函数的语法结构。
一个需要括号而另一个不需要括号的原因是因为它们是具有不同语法的两种独立的语言构造,而不是调用构造函数的两种方法。
请注意,如果您在第二个示例中添加括号,您实际上声明了一个函数而不是定义了一个变量:
std::list<int> foo; //variable definition
std::list<int> foo(); //function taking no args, returning a std::list<int>
这通常被称为最麻烦的解析 https://stackoverflow.com/questions/1424510/most-vexing-parse-why-doesnt-a-a-work。 C++11 引入了花括号初始化来解决这个问题:
std::list<int> foo{}; //variable definition
标准语,对于那些愿意的人
(引自N3337)
"But T()
当然看起来像构造函数调用,为什么不是呢?”
在这种背景下,T()
称为带有函数符号的显式类型转换:
5.2.3 显式类型转换(函数表示法)[expr.type.conv]
1 [...]
2 表达方式T()
, where T
是非数组完整对象类型或(可能是 cv 限定的)void 类型的简单类型说明符或类型名称说明符,创建指定类型的纯右值,该类型是值初始化的(8.5;对于 void() 情况不进行初始化)。 [注:如果T
是一个非类类型
cv 限定符,在确定结果纯右值 (3.10) 的类型时忽略 cv 限定符。 ——尾注]
所以这创建了一个prvalue这是值初始化.
[dcl.init]/7:
To 值初始化类型的对象T
means:
— 如果 T 是一个(可能是 cv 限定的)类类型(第 9 条),具有用户提供的构造函数 (12.1),则调用 T 的默认构造函数(如果 T 没有可访问的默认值,则初始化是错误的
构造函数);
— [...]
因此,这将调用构造函数作为值初始化的一部分,这是显式类型转换的一部分。如上所述,无法直接调用构造函数。标准说:
[class.ctor]/1:
构造函数没有名字。使用特殊的声明符语法来声明或定义构造函数。
语法使用:
— 可选的 decl-specifier-seq,其中每个 decl-specifier 是函数说明符或 constexpr,
— 构造函数的类名,以及
— 参数列表
以该顺序。在这样的声明中,构造函数类名周围的可选括号将被忽略。
因此构造函数没有名称,我们使用语言定义的语法异常来声明/定义它们。
“这看起来像是一种学术上的区别,这在实践中重要吗?”
也许,也许不是。我的观点是,将上述语法解释为纯构造函数调用会错误地描述构造函数是什么。构造函数初始化一个对象;它不会分配该对象的内存、返回初始化的对象、将符号绑定到该对象或通过变量定义和类型转换完成的任何其他操作。此外,它可能会造成像OP一样的混乱,OP希望统一语法,因为他认为这两个构造都是构造函数调用。
当我们有避免混淆的正式术语时,为什么要使用不精确的提喻法呢?