带和不带构造函数调用的区别() [英] Difference between constructor calls with and without ( )

查看:129
本文介绍了带和不带构造函数调用的区别()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是一个C ++初学者,想了解为什么

  return std :: list< int>(); 

需要括号,但

  std :: list< int> foo; 

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

解决方案

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



第一种是显式类型转换,它创建一个对象 std :: list< int>



第二个是一个变量定义, std :: list< int>



默认构造函数



虽然你可能会看到这样的事情被称为构造函数调用,但没有句法结构来显式地和单独地调用C ++中的构造函数。



另一个不需要括号的原因是因为它们是具有不同语法的两个单独的语言结构,而不是两种调用构造函数的方法。






请注意,如果在第二个示例中添加括号,则实际上声明了一个函数,而不是定义一个变量:

  std :: list< int> foo; // variable definition 
std :: list< int> foo(); //函数不带参数,返回std :: list< int>

这通常被称为 most-vexing-parse 。 C ++ 11引入了支撑初始化来解决这个问题:

  std :: list< int& foo {}; //变量定义






倾斜



(来自N3337)



c



在这种情况下, T ()被称为具有函数符号的显式类型转换:


5.2.3显式类型



1 [...]

p> 2 表达式 T(),其中 T 是一个简单类型说明符或typename-非数组完整对象类型的说明符或(可能是cv限定的)void类型,创建指定类型的prvalue,它是值初始化的(8.5;不对void )。 [注意:如果 T
cv-qualified的非类类型,那么在确定结果prvalue的类型时将忽略cv-qualifiers )。 -end note]


这样就创建了一个 prvalue >。


[dcl.init] / 7: -initialize 类型 T 的对象表示:



- 如果T是可能cv限定)类类型(条款9)与用户提供的构造函数(12.1),那么T的默认构造函数称为(如果T没有可访问的默认
constructor);



- [...]


调用构造函数作为值初始化的一部分,这是显式类型转换的一部分。如上所述,没有办法直接调用构造函数。标准说:


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



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



- 构造函数的类名和



- 参数列表



。在这样的声明中,构造函数类名称周围的可选括号将被忽略。


所以构造函数没有名称,



这似乎是学术上的区别,实际上是否重要?



也许,也许不是。我的意见是,解释语法像上面的纯构造函数调用绘制一个不正确的构造函数的图片。构造函数初始化对象;它不分配对象的内存,返回初始化的对象,绑定一个符号到该对象或任何其他由变量定义和类型转换完成。此外,它可以产生像OP的混乱,他希望统一的语法,因为他认为这两个结构都是构造函数调用。



为什么使用不精确的synecdoche当我们有正式避免混淆的术语?


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?

解决方案

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?

这篇关于带和不带构造函数调用的区别()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆