【问题标题】:Why copy constructor is called in std::vector's initializer list?为什么在 std::vector 的初始化列表中调用复制构造函数?
【发布时间】:2016-07-25 01:37:16
【问题描述】:

我有以下非常简单的类:

class Foo 
{
public:
  Foo() {}
  Foo(const Foo&) = delete;
  Foo(Foo&&) {}

  void operator=(const Foo&) = delete;
  void operator=(Foo&&) {}

  void dump() const {}
};

该类是可移动构造和可分配的,但不是可复制构造和可分配的。

我想使用向量的初始化列表来初始化 Foo 元素的向量。

std::vector<Foo> vf = { Foo() };

编译器抱怨,因为代码必须使用已删除的复制构造函数。谁能解释一下,为什么在这种情况下不使用 move 构造,以及为什么需要对象的副本?

以下也需要复制构造函数,也不起作用:

std::vector<Foo> vf = { std::move(Foo()) };

另一方面,这很好用(调用了移动构造函数):

std::vector<Foo> vf;
vf.push_back(Foo());

感谢您的解释... :)

更新:

建议的this 帖子解释了我的问题。

此外,让我们考虑以下代码(连同上面的class Foo):

class Bar {
public:
    Bar(std::initializer_list<Foo> _l) {
        std::cout << "Bar::Bar()" << std::endl;
        for (auto& e : _l)
            e.dump();
    }
};

int main() {
    Bar b { Foo() };
    return 0;
}

这会产生以下输出(使用 C++11 编译):

Foo::Foo()
Bar::Bar()
Foo::dump()
Foo::~Foo()

可以看出,初始化列表实际上并没有用大括号之间的“对象副本”填充。从 C++14 开始,这可能不是真的。

【问题讨论】:

标签: c++ move-semantics initializer-list


【解决方案1】:

正是因为您使用了initializer list,才使用了复制构造函数。初始化器列表使用对象的副本进行初始化,然后传递给向量。

如果你阅读了链接的参考,它甚至会明确地说出是从 C++14 开始的

...每个元素都是复制初始化的 ...从原始初始化列表的相应元素

强调我的

【讨论】:

  • “初始化列表是用对象的副本初始化的”——真的是这样吗?这里的问题不是“initializer_list 的开始和结束返回 const T *”(根据 stackoverflow.com/a/8193157/388661),因此您不能 项移出初始化程序列表吗?
  • 嗯,刚刚做了一个快速测试。使用包含单个对象的初始化器列表初始化向量确实会导致复制构造函数被调用两次。在我看来,这里存在语言设计问题!赞成。
  • @davmac 同样来自参考:“std::initializer_list&lt;T&gt; 类型的对象是一个轻量级代理对象,它提供对const T 类型对象的 array 的访问。”因为使用了“数组”,所以需要复制元素,这就是禁止移动的原因。
  • 知道了,不过我更相信实际语言标准的引用。在我看来,如果允许实现在适当的位置(在支持数组内)构造对象并且(至少可能)允许将对象移出,从而省略两个副本,那会更好。我看不出有什么技术原因不能这样。
  • 这个答案虽然偶然正确,但前提是错误的。复制初始化(即使在标准中)指的是复制移动。您可能会注意到您链接的同一页面上可疑地缺少“移动初始化”。
【解决方案2】:

std::initializer_list 不适用于仅移动类型。详情请见this question

幸运的是,有一个非常简单的解决方法:

std::vector<foo> vf (1);

这将使用 1 个默认构造的 foo 初始化向量。

【讨论】:

  • 好吧,可能我的示例代码过于简化了。这段代码的目的不仅仅是在vf 中创建单个元素,而是为了演示我的问题。
【解决方案3】:

一般来说,对std::vector 的元素的要求是元素类型是完整类型并且满足可擦除的要求。因此,您的对象没有问题。

但是,使用初始化列表进行初始化:

std::vector<Foo> vf = { Foo() };

要求将列表中的临时对象Foo() 复制到向量中。因此,编译器会抱怨,因为您已经使您的对象不可复制。

你可以通过以下方式完成你想要的;

std::vector<Foo> vf(1);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-03-26
    • 2020-10-28
    • 1970-01-01
    • 2020-05-05
    • 2014-09-07
    • 2018-01-13
    相关资源
    最近更新 更多