使用和不使用()的构造函数调用之间的区别

use*_*614 21 c++ constructor

我是C++初学者,想了解原因

return std::list<int>();
Run Code Online (Sandbox Code Playgroud)

需要括号,但是

std::list<int> foo;
Run Code Online (Sandbox Code Playgroud)

不需要括号.这些构造函数调用之间有什么区别?

Tar*_*ama 22

这些都不是构造函数调用.

第一种是显式类型转换,它创建一个类型的对象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>
Run Code Online (Sandbox Code Playgroud)

这通常被称为最令人烦恼的解析.C++ 11引入了braced-initialization来解决这个问题:

std::list<int> foo{}; //variable definition
Run Code Online (Sandbox Code Playgroud)

Standardese,对于那些如此倾向的人

(引自N3337)

"但是T()肯定看起来像构造函数,为什么不呢?"

在该上下文中,T()称为带功能表示法的显式类型转换:

5.2.3显式类型转换(功能表示法)[expr.type.conv]

1 [...]

2 表达式T(),其中T是非数组完整对象类型的简单类型说明符或类型名称说明符,或者(可能是cv限定的)void类型,创建指定类型的prvalue,它是值初始化的(8.5 ;没有为void()情况进行初始化.[注意:如果T是具有cv限定的非类型类型,则在确定结果prvalue(3.10)的类型时将忽略cv限定符. - 尾注]

因此,这将创建一个prvalue值初始化.

[dcl.init]/7:值初始化类型的对象T意味着:

- 如果T是具有用户提供的构造函数(12.1)的(可能是cv限定的)类类型(第9节),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化是错误的);

- [...]

因此,这将构造函数作为值初始化的一部分进行调用,这是显式类型转换的一部分.如上所述,无法直接调用构造函数.标准说:

[class.ctor]/1: 构造函数没有名称.特殊的声明符语法用于声明或定义构造函数.语法使用:

- 一个可选的decl-specifier-seq,其中每个decl-specifier都是一个函数说明符或constexpr,

- 构造函数的类名,和

- 参数列表

以该顺序.在这样的声明中,忽略构造函数类名称周围的可选括号.

因此构造函数没有名称,我们使用语言定义的语法异常声明/定义它们.

"这似乎是一个学术上的区别,这在实践中是否重要?"

也许,也许不是.我的观点是,将上述语法解释为纯构造函数调用会描绘构造函数的错误图像.构造函数初始化一个对象; 它不会分配该对象的内存,返回初始化对象,将符号绑定到该对象或由变量定义和类型转换完成的任何其他操作.此外,它可能会产生类似于OP的混淆,因为他认为这两个构造都是构造函数调用,因此他们期望统一语法.

当我们有正式条款避免混淆时,为什么要使用不精确的提示?

  • 虽然正确,但我认为使用完整的技术术语对初学者没有帮助.例如,什么是prvalue? (6认同)
  • 它通常被称为最令人烦恼的解析,但并不准确. (4认同)
  • @TheOperator:执行"代码"会导致调用构造函数_among其他的东西_.程序员没有直接调用构造函数.事实上,这是不可能的.我没有看到这是多么"混乱"或"误导".就像......你不会说"brb,我会通过鼓励生产牛肉来参与温室气体的稳定增加"......你说"吃汉堡".这是语义. (4认同)
  • @Lingxi:"调用构造函数"通常被理解为直接函数调用,类型名称作为函数名称给出(通常是函数强制转换符号/临时对象创建被误解).没有语法可以执行该操作.指示程序调用构造函数的唯一方法是作为其他更复杂语义的一部分; placement new是这些复杂操作的一个例子.对某些人来说,这似乎是一种迂腐,但事实上,这种严谨性对于正确理解语言的结构以及程序的正确性是必要的. (4认同)
  • @TartanLlama:我的意思是,这不是最令人烦恼的解析,但它经常被误认为是这样. (3认同)
  • *有这样的语法结构,用于在C++中明确地和单独地调用构造函数*如何放置new? (2认同)

Dav*_*aim 10

这样看:
1)你需要创建一个对象
2)你需要返回它.

让我们说编译器查看表达式return Foo;,编译器认为"嘿!他希望我返回一个类型!类型不是我可以返回的东西!我需要一个真正的变量!"

所以你可以写点类似的东西

Foo temp;
return temp;
Run Code Online (Sandbox Code Playgroud)

或者缩短它 - 调用默认构造函数Foo,然后返回刚刚创建的Anonymous对象.您将构造函数视为生成对象的函数.

代码return createDefaultFoo();看起来更合理吗?好吧,这是做什么的Foo(),它创建并返回匿名的Fooobejct

在这一行:

std::list<int> foo;
Run Code Online (Sandbox Code Playgroud)

编译器可以告诉你想要一个foo从该类型命名的对象std::list<int>.所以这()是冗余的.在这里回答,添加()将使编译器认为您声明了一个函数.


Nic*_*ick 8

两个语句都调用默认构造函数.

return std::list<int>();
Run Code Online (Sandbox Code Playgroud)

这与:

std::list<int> value;
return value;
Run Code Online (Sandbox Code Playgroud)

这里创建一个对象(使用默认构造函数)并返回对象.

std::list<int> foo;
Run Code Online (Sandbox Code Playgroud)

这里foo使用默认构造函数创建对象.

以下是其他方法C++11:

std::list<int> foo;
std::list<int> foo1{}; // C++11
Run Code Online (Sandbox Code Playgroud)