【问题标题】:How to initialize array of classes with deleted copy constructor (C++11)如何使用已删除的复制构造函数初始化类数组(C++11)
【发布时间】:2014-12-28 09:58:38
【问题描述】:

Why can't I initialise an array of objects if they have private copy constructors? 上的现有问题专门针对 C++03。我从那个问题中知道我正在尝试做的事情在 C++03 中是不允许的,但我认为在 C++11 中应该是可能的

我有一个不可移动的类(称为子类),我需要在另一个类的构造函数中初始化一个子类数组(称为父类)。 “不可移动”是指子对象的地址在该对象的生命周期内必须保持不变。这样做的正确方法是什么?

使用 C++11,我尝试了以下方法:

class Child
{
public:
    Child (int x) {}
    ~Child () {}

    Child (const Child &) = delete;
};

class Parent
{
public:
    Parent () : children {{5}, {7}} {}

private:
    Child children[2];
};

这段代码在 Clang 3.5.0 中编译得很好,但是 GCC 4.9.1 抱怨我正在尝试使用已删除的复制构造函数:

test.cc: In constructor ‘Parent::Parent()’:
test.cc:13:35: error: use of deleted function ‘Child::Child(const Child&)’
     Parent () : children {{5}, {7}} {}
                                   ^
test.cc:7:5: note: declared here
     Child (const Child &) = delete;
     ^

我已经了解了复制初始化和直接初始化之间的区别(例如herehere),并且我想避免使用直接初始化来调用复制构造函数。我的语法错了吗?这是 GCC 中的错误吗?还是我试图做的事情是不可能的?

【问题讨论】:

  • 在我看来这是一个 clang 错误,而不是 gcc 错误。如果您将代码更改为children {Child{5}, Child{7}},clang 将无法编译代码,它的行为应该与您发布的内容相同。一种解决方法是使用 vector 并放置 Child 对象。
  • g++ 以 Child children[2] { {5}, {7} }; 成功,这应该与 ctor 初始化程序列表中出现相同初始化程序的版本相同;两者都包含在 [dcl.init.list]/3
  • 通读初始化部分,这段代码似乎是正确的; children[2] = { {5}, {7} } 表示children[0] 是从{5} 复制初始化的,即它与Child c = { 5 }; 相同,并且再次由[dcl.init.list] 覆盖,它调用c 的构造函数,该构造函数采用@ 987654336@(不涉及副本)。
  • @Praetorian: children {Child {5}, Child {7}} 是不同的,我想。将Child {5} 放在大括号内会强制编译器(名义上)创建一个临时对象,然后将其复制以初始化数组。
  • 创建gcc.gnu.org/bugzilla/show_bug.cgi?id=63707。我们将看看 GCC 开发人员怎么说。

标签: c++ arrays c++11 gcc clang


【解决方案1】:

我同意 cmets 的观点,这似乎是一个 GCC 错误(报告为 63707)。

只有当数组中的类型有用户定义的析构函数时才会编译失败,这对我来说没有意义。

【讨论】:

  • 有时它在没有析构函数的情况下也会失败:stackoverflow.com/questions/31906483/…
  • @peku33,是的,我在 GCC 错误报告中给出了一个类似的示例。问题发生在非平凡的析构函数上,而不仅仅是用户定义的析构函数。
【解决方案2】:

我遇到了类似的问题,即这段代码

#include <iostream>

class Widget {
public:
    Widget(int i) { std::cout << "Ctor " << i << std::endl; }

    Widget(const Widget&); // = delete;
};

int main() {
    Widget w = 123;
}

已编译并给出了预期的结果,但在取消注释 = delete 后,它无法使用 gcc-4.9 进行编译。

阅读标准后,我相信答案在于8.5/16 中缩进量最高的第二项,如下所示。

基本上似乎发生的是,编译器在概念上想要创建一个 Widget 类型的临时对象,并直接初始化实际对象 w 从该临时对象通过复制构造函数。由于复制构造函数被删除,编译停止。如果复制构造函数没有被删除,编译器稍后会意识到它可能会删除副本,但它并没有走到那一步。

以下是相关部分:

[...] 用于 [...] 复制初始化案例 [...] 可以从源类型转换为目标类型的用户定义转换序列 [...] 被枚举,如中所述13.3.1.4,通过重载决议(13.3)选择最好的一个。 [...] 使用初始化表达式作为参数调用所选函数;如果函数是构造函数,则调用初始化目标类型的 cv 非限定版本的临时版本。 [...]然后根据上述规则,调用的结果(这是构造函数案例的临时结果)用于直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过构造 中间结果直接放入正在初始化的对象中;见 12.2、12.8。

但我可能错了,因为[...] 部分中有很多我不明白的地方。

【讨论】:

  • 我不认为这真的相关。 Widget w = 123; 使用复制构造函数,Widget w{123}; 是直接初始化。
  • 这绝对是另一种情况。在堆栈上创建Widget 需要一个可访问的析构函数,因为它会为堆栈对象自动调用。如果您有已删除的析构函数,则无法在堆栈上创建它。这与问题无关。
  • @JonathanWakely 在我的示例中删除的不是析构函数。
  • 你是对的,对不起。不过还是有不同的情况。 Widget w = 123; 表示 Widget w = Widget(123) 需要一个复制构造函数。这是 C++ 的一个众所周知的特性,与 C++11 无关,与本题无关。
  • @JonathanWakely 我理解了标准的引用部分(在我写这篇文章的时候),也谈到了 OP 中的情况。我建议失败的行等效于Parent () : children { Child{5}, Child{7}} {},然后从临时对象中初始化实际的数组元素。但也许我搞错了。
猜你喜欢
  • 1970-01-01
  • 2015-06-09
  • 2020-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多