【问题标题】:copy-list-initialization of non-copyable types不可复制类型的复制列表初始化
【发布时间】:2012-11-21 13:05:50
【问题描述】:

12.6.1 - 显式初始化

struct complex {
  complex();
  complex(double);
  complex(double,double);
};

complex sqrt(complex,complex);

complex g = { 1, 2 };  // construct complex(1, 2) 
                       // using complex(double, double) 
                       // and *copy/move* it into g

8.5 初始化器

14 - 表单中发生的初始化

T x = a;

以及参数传递、函数返回、抛出异常 (15.1)、处理异常 (15.3)、聚合成员 初始化 (8.5.1) 称为复制初始化。 [笔记: 复制初始化可能会调用移动(12.8)。 ——尾注]

15 - 表单中发生的初始化

T x(a);

T x{a};

以及在新表达式 (5.3.4) 中的 static_cast 表达式 (5.2.9),功能符号类型转换(5.2.3),以及基础和 成员初始化器 (12.6.2) 称为直接初始化

8.5.4 列表初始化 [dcl.init.list]

1 - 列表初始化是对象或引用的初始化 一个花括号初始化列表。这样的初始化器称为初始化器列表, 列表中以逗号分隔的初始化子句称为 初始化列表的元素。初始化列表可能为空。 列表初始化可以发生在直接初始化或复制初始化中 上下文;列表初始化 直接初始化上下文称为直接列表初始化和 复制初始化上下文中的列表初始化被称为 复制列表初始化。

原子问题

29.6.5 原子类型操作的要求 [atomics.types.operations.req]

#define ATOMIC_VAR_INIT(value)见下文

宏扩展为适合常量的记号序列 a 的静态存储持续时间的原子变量的初始化 与值初始化兼容的类型。 [注:这 操作可能需要初始化锁。 — 尾注] 并发访问 到被初始化的变量,即使是通过原子操作, 构成数据竞赛。 [ 例子:

atomic<int> v = ATOMIC_VAR_INIT(5);

根据前面的部分,似乎不应该在没有涉及复制构造函数的情况下进行赋值初始化,即使它根据 §12.8.31 和 §12.8.32 被省略,但原子定义为:

29.5 原子类型 [atomics.types.generic]

atomic() noexcept = default;
constexpr atomic(T) noexcept;
atomic(const atomic&) = delete;
atomic& operator=(const atomic&) = delete;
atomic& operator=(const atomic&) volatile = delete;
T operator=(T) volatile noexcept;
T operator=(T) noexcept;

没有复制构造函数!

ATOMIC_VAR_INIT 通常会扩展为大括号表达式以进行大括号初始化,但 atomic<int> v = {5} 仍然是赋值初始化,并且会暗示在直接构造临时变量之后进行复制构造。

我查看了“常量初始化”部分,看看是否存在允许在没有副本的情况下执行此操作的漏洞(因为“宏扩展为一个令牌序列,适合于对静态存储持续时间的原子变量进行常量初始化与值初始化兼容的类型”)但我已经放弃了。

相关讨论:

http://thread.gmane.org/gmane.comp.lib.qt.devel/8298

http://llvm.org/bugs/show_bug.cgi?id=14486

编辑

在构建推理过程时引用相关标准部分的答案将是理想的。

结论

因此,在 Nicol Bolas 给出了很好的回答之后,有趣的结论是 complex g = { 1, 2 } 是一个副本(它是复制初始化上下文),它不复制(复制列表初始化像直接列表初始化一样解析) ) 标准建议有一个复制操作 (12.6.1: ...and copy/move it into g)。

修复

拉取请求:https://github.com/cplusplus/draft/pull/37

【问题讨论】:

  • 这是很多标准的报价。您的实际问题是什么?
  • "这是 c++11 标准的缺陷吗?"然后查看“原子问题”。在删除复制构造时,它看起来像引用 atomic v = ATOMIC_VAR_INIT(5) 的冲突。
  • 我认为这个问题可以简化为“复制列表初始化是否意味着复制初始化?”,举个简短的例子。
  • @KerrekSB 我有我的办法,我已经在给出的​​答案中提到了引号。
  • 我认为this question 可能是相关的。

标签: c++ c++11


【解决方案1】:
complex g = { 1, 2 };  // construct complex(1, 2) 
                       // using complex(double, double) 
                       // and *copy/move* it into g

这是不真实的。我并不是说复制/移动会被忽略;我的意思是不会有复制或移动。

您引用了 8.5 p14,它将T x = a; 定义为复制初始化。这是真实的。但它接着定义了初始化的实际工作方式:

从 8.5 开始,p16:

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

  • 如果初始化程序是(非括号)大括号初始化列表,则对象或引用是列表初始化的 (8.5.4)。

这意味着 copy-initialization 规则不适用braced-init-list。它们使用一组单独的规则,如 8.5.4 所述。

您引用了 8.5.4,它将 T x = {...}; 定义为 copy-list-initialization。您的推理出错的地方在于您从未查看过 copy-list-initialization 实际上做了什么。没有复制;这就是它的名称​​

copy-list-initializationlist-initialization 的一个子集。因此,它遵循 8.5.4, p3 规定的所有规则。我不打算在这里引用它们,因为它们有几页长。我将按顺序简单解释规则如何应用于complex g = {1, 2};

  1. 初始值设定项列表包含元素,因此此规则不计算在内。
  2. complex 不是一个聚合,所以这条规则不算数。
  3. complex 不是 initializer_list 的特化,所以这条规则不算数。
  4. 根据 13.3 和 13.3.1.7 的规则,通过重载决议考虑适用的构造函数。这会找到接受两个双精度的构造函数。

因此,不会创建和复制/移入临时文件。

copy-list-initializationdirect-list-initialization 的唯一区别在 13.3.1.7, p1:

[...] 在复制列表初始化中,如果选择显式构造函数,则初始化格式错误。

这是complex g{1, 2}complex g = {1, 2} 之间的唯一区别。它们都是list-initialization 的示例,除了使用显式构造函数外,它们以统一的方式工作。

【讨论】:

  • 看起来确实没问题,即使它有点隐式复制列表初始化应该跟直接列表初始化一样。此外,标准中的“复杂”代码示例只会让人感到困惑(那里确实存在缺陷)。但我认为它们的相关性不如您的假设,谢谢。
  • @Chico:这听起来更像an editorial issue,而不是缺陷,因为它只存在于非规范代码中。所以在上面提交一个 GitHub 错误。
  • 只是为了补充。根据这个过程,atomic<int> v = {5} 解析为左侧的单个构造函数,即 constexpr 构造函数。这确实是一个非常简单的表达,通过非常曲折的手段和微妙的规则才有效。如果你把大括号拿出来,它就不再有效了,因为@kerrek-sb 也指出了;如果它不是像 5 这样的常量/字面量,那么它也是无效的。
  • @Chico:那条线不起作用,因为它是“常量/文字”。它之所以有效,是因为有问题的类有一个匹配的构造函数,它不是explicit。它是 constexpr 构造函数的事实是无关紧要的。您可以在 braced-init-list 中传递一个整数变量,它仍然可以工作,因为constexpr 构造函数没有必须 使用常量表达式调用。跨度>
【解决方案2】:

constructor-from-T显式的,copy-list-initialization 与 copy-initialization 不同。两者都导致“考虑构造函数”,但复制初始化总是“考虑”复制构造函数,而列表初始化考虑填充列表元素的构造函数(加上一些细节)。也就是说:

struct Foo
{
    Foo(int) {}
    Foo(Foo const &) = delete;
};

int main()
{
    Foo f = { 1 };  // Fine
}

(如果构造函数是explicit,这将失败。另外,Foo x = 1; 当然会因为已删除的复制构造函数而失败。)

也许是一个更有启发性的用例:

Foo make() { return { 2 }; }

void take(Foo const &);
take(make());

为此所需的一切都在 8.5.4/3 和 13.3.1.7/1 中。

【讨论】:

  • 我会看看那个部分。关键是,我不是断言复制列表初始化与复制初始化相同,这就是我引用列表初始化部分的原因。关键是,在标准中,有一点推翻了引用的 8.5.14 和 8.5.15 进行列表初始化。
  • @Chico:不同之处在于Foo f = 1; 会像Foo f = Foo(1); 那样导致用户定义的转换,因此将匹配复制构造函数。相比之下,列表初始化将匹配另一个构造函数,如Foo f(1);。查看更新。
  • 我真的很想看看你的演绎过程加上引用相关标准部分(不是链接)。
猜你喜欢
  • 2011-10-11
  • 2011-09-25
  • 2014-03-16
  • 1970-01-01
  • 2014-04-22
  • 1970-01-01
  • 2018-10-29
  • 1970-01-01
  • 2021-02-03
相关资源
最近更新 更多