为什么就地成员初始化在C ++ 11中使用副本构造函数? [英] Why does an in-place member initialization use a copy constructor in C++11?

查看:100
本文介绍了为什么就地成员初始化在C ++ 11中使用副本构造函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对以下代码有些困惑:

I'm a little bit confused about the following code:

struct A {
  std::atomic<int> a = 0;
};

哪个给出错误:

复制类型为'std :: atomic'的成员子对象会调用已删除的构造函数

copying member subobject of type 'std::atomic' invokes deleted constructor

但是几乎相同的代码确实起作用:

But almost the same code does work:

struct A {
  std::atomic<int> a = {0};
};

Okey,如果第一个变体需要复制构造函数,则必须使用operator=().可是等等!该运算符无需复制构造函数即可完美工作:

Okey, if the first variant requires the copy constructor, then it have to use operator=(). But wait! This operator perfectly works without the copy constructor:

A a;
a.a = 1;

有人能解释简单操作如何扩展两个就地初始化吗?为什么第一个需要复制构造函数?

Can anyone explain how both of the in-place initializations are expanded in terms of simple operations? Why the first one requires copy constructor?

推荐答案

所有参考均参考N3797,即C ++ 1y当前的工作草案. §8.5初始化程序[dcl.init]/15状态:

All references are to N3797, the C++1y current working draft. §8.5 Initializers [dcl.init]/15 states:

以形式发生的初始化

The initialization that occurs in the form

T x = a;

以及参数传递,函数返回,引发异常(15.1),处理异常 (15.3),聚合成员初始化(8.5.1)称为 copy-initialization . [注意:复制初始化可能会调用移动(12.8). —尾注]

as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization. [ Note: Copy-initialization may invoke a move (12.8). —end note ]

所以声明:

std::atomic<int> a = 0;

正在执行复制初始化.根据8.5/17:

is performing copy-initialization. According to 8.5/17:

初始化器的语义如下. 目标类型是要初始化的对象或引用的类型,源类型是初始化器表达式的类型.如果初始化程序不是单个(可能带有括号)表达式,则未定义源类型.

The semantics of initializers are as follows. The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly parenthesized) expression, the source type is not defined.

此处的目标类型std::atomic<int>,而源类型int(即decltype(0)).要确定初始化的语义,我们必须确定第17段的项目符号适用于哪个:

The destination type here is std::atomic<int> and the source type is int (i.e., decltype(0)). To determine the semantics of the initialization, we have to determine which of paragraph 17's bullets applies:

  • 如果初始化程序是(无括号的)括号初始化列表,则对象或引用将进行列表初始化(8.5.4).
  • 如果目标类型是引用类型,请参见8.5.3.
  • 如果目标类型是字符数组,char16_t数组,char32_t数组或wchar_t数组,并且初始化程序是字符串文字,请参见8.5.2.
  • li>
  • 如果初始化程序为(),则该对象将被值初始化.
  • 否则,如果目标类型是数组,则程序格式错误.
  • 如果目标类型是(可能是cv限定的)类类型:
    • 如果初始化是直接初始化,或者如果是复制初始化,则源类型的cv不合格版本与目标的类相同,或者是该类的派生类,... [不适用,源类型为int]
    • 否则(例如,对于其余的复制初始化情况),用户定义的转换序列可以从源类型转换为目标类型,或者(当转换函数时 如第13.3.1.4节中所述,对其枚举类别进行枚举,并通过重载分辨率(13.3)选择最佳类别.如果转换无法完成或模棱两可,则 初始化格式错误.所选函数以初始化器表达式作为参数进行调用;如果函数是构造函数,则调用将初始化cv-unqualified的临时 目标类型的版本.临时变量是一个prvalue.通话结果(即 然后,根据上述规则,将其用于直接初始化) 作为复制初始化目标的对象.在某些情况下, 允许通过构造以下子项来消除此直接初始化中固有的复制: 中间结果直接放入正在初始化的对象中;参见12.2、12.8.
  • If the initializer is a (non-parenthesized) braced-init-list, the object or reference is list-initialized (8.5.4).
  • If the destination type is a reference type, see 8.5.3.
  • If the destination type is an array of characters, an array of char16_t, an array of char32_t, or an array of wchar_t, and the initializer is a string literal, see 8.5.2.
  • If the initializer is (), the object is value-initialized.
  • Otherwise, if the destination type is an array, the program is ill-formed.
  • If the destination type is a (possibly cv-qualified) class type:
    • If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, ... [does not apply, source type is int]
    • Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. 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 cv-unqualified version of the destination type. The temporary is a prvalue. 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.

我们在那里.通过创建使用std::atomic<int>(int)构造函数初始化的临时对象,将初始化程序表达式0转换为std::atomic<int>.该临时对象用于直接初始化原始的std::atomic<int>对象.我们之前忽略的另一个(可能是经过cv限定的)类类型"项目符号现在适用:

There we are. The initializer expression - 0 - is converted into a std::atomic<int> via the creation of a temporary object initialized with the std::atomic<int>(int) constructor. That temporary object is used to direct-initialize the original std::atomic<int> object. The other of the "(possibly cv-qualified) class type" bullets that we ignored before now applies:

  • 如果初始化是直接初始化,或者如果复制是初始化,其中源类型的cv不合格版本与目标的类相同,或为该类的派生类,则考虑构造函数.列举了适用的构造函数(13.3.1.3),并通过重载分辨率(13.3)选择了最佳的构造函数.如此选择的构造函数将被调用以初始化对象,并使用初始化表达式或 expression-list 作为其参数.如果没有适用的构造函数,或者重载解决方案不明确,则初始化格式不正确.
  • If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.

回想一下,新的初始化程序是prvalue std::atomic<int>.重载解决方案确定没有合适的std::atomic<int>构造函数接受单个参数std::atomic<int>&&(std::atomic<int>不可移动或不可复制)并诊断程序格式错误.

Recall that the new initializer is a prvalue std::atomic<int>. Overload resolution determines that there is no appropriate std::atomic<int> constructor that accepts a single argument std::atomic<int>&& (std::atomic<int> is not movable or copyable) and diagnoses the program as ill-formed.

问题的第二部分,

std::atomic<int> a = {0};

再次按照8.5/15复制初始化.但是,这一次适用于8.5/17的第一个项目符号:

is again copy initialization per 8.5/15. This time, however, the very first bullet of 8.5/17 applies:

  • 如果初始化程序是(无括号的)括号初始化列表,则对象或引用将进行列表初始化(8.5.4).
  • If the initializer is a (non-parenthesized) braced-init-list, the object or reference is list-initialized (8.5.4).

对于 list-initialization ,我们必须查看8.5.4/3:

For list-initialization, we must look to 8.5.4/3:

对象或类型为T的引用的列表初始化定义如下:

List-initialization of an object or reference of type T is defined as follows:

  • 如果T是聚合,则执行聚合初始化(8.5.1).
  • 否则,如果初始化器列表中没有元素,并且T是具有默认构造函数的类类型,则该对象将被值初始化.
  • 否则,如果Tstd::initializer_list<E>的特化,则按如下所述构造prvalue initializer_list对象,并根据同类型类的对象初始化规则将其用于初始化对象(8.5).
  • 否则,如果T是类类型,则考虑构造函数.列举了适用的构造函数,并通过重载决议(13.3、13.3.1.7)选择了最佳的构造函数.如果要转换任何参数都需要缩小转换(请参见下文),则程序格式错误.
  • ...
  • If T is an aggregate, aggregate initialization is performed (8.5.1).
  • Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
  • Otherwise, if T is a specialization of std::initializer_list<E>, a prvalue initializer_list object is constructed as described below and used to initialize the object according to the rules for initialization of an object from a class of the same type (8.5).
  • Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed.
  • ...

std::atomic<int>是类类型,而不是聚集或initializer_list专业化,因此考虑使用构造函数. std::atomic<int>::atomic(int)构造函数将被选为完美匹配,并用于初始化对象.

std::atomic<int> is a class type, not an aggregate or initializer_list specialization, so constructors are considered. The std::atomic<int>::atomic(int) constructor will be selected as a perfect match and is used to initialize the object.

这篇关于为什么就地成员初始化在C ++ 11中使用副本构造函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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