执行这两个 C++初始化语法在语义上有什么不同吗?

发布于 2024-10-18 12:09:58 字数 368 浏览 0 评论 0原文

假设以下代码是可以正确编译的合法代码,T 是类型名称,x 是变量名称。

语法一:

T a(x);

语法二:

T a = x;

这两个表达式的确切语义是否有所不同?如果是的话,是在什么情况下?

如果这两个表达式确实具有不同的语义,我也很好奇标准的哪一部分讨论了这一点。

另外,如果存在特殊情况,T 是标量类型的名称(又名 intlongdouble 等。 .),当 T 是标量类型与非标量类型时有什么区别?

Assume that the following code is legal code that compiles properly, that T is a type name, and that x is the name of a variable.

Syntax one:

T a(x);

Syntax two:

T a = x;

Do the exact semantics of these two expressions ever differ? If so, under what circumstances?

If these two expressions ever do have different semantics I'm also really curious about which part of the standard talks about this.

Also, if there is a special case when T is the name of a scalar type (aka, int, long, double, etc...), what are the differences when T is a scalar type vs. a non-scalar type?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

傾城如夢未必闌珊 2024-10-25 12:09:58

是的。如果 x 的类型不是 T,则第二个示例将扩展为 T a = T(x)。这要求 T(T const&) 是公共的。第一个示例不调用复制构造函数。

检查可访问性后,可以消除副本(正如托尼指出的)。但是,在检查可访问性之前,无法消除它。

Yes. If the type of x is not T, then the second example expands to T a = T(x). This requires that T(T const&) is public. The first example doesn't invoke the copy constructor.

After the accessibility has been checked, the copy can be eliminated (as Tony pointed out). However, it cannot be eliminated before checking accessibility.

嘿咻 2024-10-25 12:09:58

这里的区别在于隐式构造和显式构造之间,并且可能存在差异。

想象一下,有一个带有构造函数 Array(size_t length) 的类型 Array,而在其他地方,你有一个函数 count_elements(const Array& array)。这些的目的很容易理解,并且代码看起来足够可读,直到您意识到它允许您调用 count_elements(2000)。这不仅是丑陋的代码,而且还会无缘无故地在内存中分配一个 2000 个元素长的数组。

此外,您可能还有其他可隐式转换为整数的类型,也允许您对这些类型运行 count_elements() ,从而以很高的效率成本给出完全无用的结果。

您想要在这里做的是将 Array(size_t length) 声明为一个显式构造函数。这将禁用隐式转换,并且 Array a = 2000 将不再是合法语法。

这只是一个例子。一旦您意识到 explicit 关键字的作用,就很容易想象出其他关键字。

The difference here is between implicit and explicit construction, and there can be difference.

Imagine having a type Array with the constructor Array(size_t length), and that somewhere else, you have a function count_elements(const Array& array). The purpose of these are easily understandable, and the code seems readable enough, until you realise it will allow you to call count_elements(2000). This is not only ugly code, but will also allocate an array 2000 elements long in memory for no reason.

In addition, you may have other types that are implicitly castable to an integer, allowing you to run count_elements() on those too, giving you completely useless results at a high cost to efficiency.

What you want to do here, is declare the Array(size_t length) an explicit constructor. This will disable the implicit conversions, and Array a = 2000 will no longer be legal syntax.

This was only one example. Once you realise what the explicit keyword does, it is easy to dream up others.

早乙女 2024-10-25 12:09:58

从 2014 年 5 月 8 日起(强调我的):

使用初始值设定项表达式作为参数来调用所选函数;如果该函数是构造函数,则调用会初始化目标类型的临时值。然后,根据上述规则,调用的结果(对于构造函数情况来说是临时的)用于直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除直接初始化中固有的复制;请参阅 class.temporary、class.copy。

因此,它们是否等效取决于实现。

8.5.11 也相关,但仅用于确认可能存在差异:

-11- 初始化的形式(使用括号或=)通常无关紧要,但当被初始化的实体具有类类型时就很重要;见下文。仅当要初始化的实体具有类类型时,带括号的初始值设定项才可以是表达式列表。

From 8.5.14 (emphasis mine):

The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the destination type. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see class.temporary, class.copy.

So, whether they're equivalent is left to the implementation.

8.5.11 is also relevant, but only in confirming that there can be a difference:

-11- The form of initialization (using parentheses or =) is generally insignificant, but does matter when the entity being initialized has a class type; see below. A parenthesized initializer can be a list of expressions only when the entity being initialized has a class type.

空心空情空意 2024-10-25 12:09:58

T a(x)直接初始化T a = x复制初始化

从标准来看:

8.5.11 初始化的形式(使用括号或 =)通常无关紧要,但当被初始化的实体具有类类型时就很重要;见下文。仅当被初始化的实体具有类类型时,带括号的初始值设定项才可以是表达式列表。

8.5.12 参数传递、函数返回、引发异常 (15.1)、处理异常 (15.3) 和大括号括起来的初始化列表 (8.5.1) 中发生的初始化称为复制初始化,并且是等效的到表格

<前><代码> T x = a;

在 new 表达式 (5.3.4)、static_cast 表达式 (5.2.9)、函数符号类型转换 (5.2.3) 以及基类和成员初始值设定项 (12.6.2) 中发生的初始化称为直接初始化,相当于表格

<前><代码> T x(a);

区别在于复制初始化会创建一个临时对象,然后将其用于直接初始化。允许编译器避免创建临时对象:

8.5.14 ... 然后,根据上述规则,调用的结果(对于构造函数而言是临时的)用于直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除直接初始化中固有的复制;参见12.2、12.8。

复制初始化需要一个非显式构造函数和一个可用的复制构造函数。

T a(x) is direct initialization and T a = x is copy initialization.

From the standard:

8.5.11 The form of initialization (using parentheses or =) is generally insignificant, but does matter when the entity being initialized has a class type; see below. A parenthesized initializer can be a list of expressions only when the entity being initialized has a class type.

8.5.12 The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization and is equivalent to the form

   T x = a;

The initialization that occurs in new expressions (5.3.4), static_cast expressions (5.2.9), functional notation type conversions (5.2.3), and base and member initializers (12.6.2) is called direct-initialization and is equivalent to the form

    T x(a);

The difference is that copy initialization creates a temporary object which is then used to direct-initialize. The compiler is allowed to avoid creating the temporary object:

8.5.14 ... The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see 12.2, 12.8.

Copy initialization requires a non-explicit constructor and a copy constructor to be available.

蹲墙角沉默 2024-10-25 12:09:58

在 C++ 中,当您编写此内容时:

class A {
  public:
  A() { ... }
};

编译器实际上会生成此内容,具体取决于您的代码使用的内容:

class A {
  public:
  A() { ... }
  ~A() { ... }
  A(const A& other) {...}
  A& operator=(const A& other) { ... }
};

所以现在您可以看到各种构造函数的不同语义。

A a1; // default constructor
A a2(a1); // copy constructor
a2 = a1; // copy assignment operator

复制构造函数基本上复制所有非静态数据。仅当生成的代码合法且健全时才会生成它们:如果编译器在类中看到他不知道如何复制的类型(根据正常的赋值规则),则不会生成复制构造函数。这意味着,如果 T 类型不支持构造函数,或者类的公共字段之一是 const 或引用类型,则生成器将不会创建它们 - 并且代码将不会构建。模板在构建时扩展,因此如果生成的代码不可构建,它将失败。有时它会失败,而且非常神秘。

如果您在类中定义构造函数(或析构函数),则生成器不会生成默认构造函数。这意味着您可以覆盖默认生成的构造函数。您可以将它们设置为私有(默认情况下它们是公共的),您可以覆盖它们以便它们不执行任何操作(对于节省内存和避免副作用很有用)等。

In C++, when you write this:

class A {
  public:
  A() { ... }
};

The compiler actually generates this, depending on what your code uses:

class A {
  public:
  A() { ... }
  ~A() { ... }
  A(const A& other) {...}
  A& operator=(const A& other) { ... }
};

So now you can see the different semantics of the various constructors.

A a1; // default constructor
A a2(a1); // copy constructor
a2 = a1; // copy assignment operator

The copy constructors basically copy all the non-static data. They are only generated if the resulting code is legal and sane: if the compiler sees types inside the class that he doesn't know how to copy (per normal assignment rules), then the copy constructor won't get generated. This means that if the T type doesn't support constructors, or if one of the public fields of the class is const or a reference type, for instance, the generator won't create them - and the code won't build. Templates are expanded at build time, so if the resulting code isn't buildable, it'll fail. And sometimes it fails loudly and very cryptically.

If you define a constructor (or destructor) in a class, the generator won't generate a default one. This means you can override the default generated constructors. You can make them private (they're public by default), you can override them so they do nothing (useful for saving memory and avoiding side-effects), etc.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文