【问题标题】:type requirements for std::vector<type>std::vector<type> 的类型要求
【发布时间】:2012-09-03 16:27:32
【问题描述】:

我仍然对在 C++11 中与 std::vector 一起使用的类型的要求感到困惑,但这可能是由错误的编译器 (gcc 4.7.0) 引起的。这段代码:

struct A {
  A() : X(0) { std::cerr<<" A::A(); this="<<this<<'\n'; }
  int X;
};

int main()
{
  std::vector<A> a;
  a.resize(4);
}

工作正常并产生预期的输出,表明调用了默认 ctor(显式给出)(而不是隐式复制 ctor)。但是,如果我将已删除的复制 ctor 添加到类中,即

struct A {
  A() : X(0) { std::cerr<<" A::A(); this="<<this<<'\n'; }
  A(A const&) = delete;
  int X;
};

gcc 4.7.0 不会编译,但会尝试使用已删除的 ctor。这是正确的行为还是错误?如果是前者,如何让代码工作?

【问题讨论】:

  • 请详细说明“预期输出”。产生了多少行输出?

标签: c++ constructor c++11 allocation stdvector


【解决方案1】:

正如其他人指出的那样,C++11 标准确实需要CopyInsertable。然而,这是 C++11 标准中的一个错误。这已在N3376 中更正为MoveInsertable 和DefaultInsertable。

vector&lt;T, A&gt;::resize(n) 成员函数需要MoveInsertable 和DefaultInsertable。当分配器 A 使用默认的 construct 定义时,这些大致转换为 DefaultConstructible 和 MoveConstructible。

以下程序使用 clang/libc++ 编译:

#include <vector>
#include <iostream>

struct A {
  A() : X(0) { std::cerr<<" A::A(); this="<<this<<'\n'; }
  A(A&&) = default;
  int X;
};

int main()
{
  std::vector<A> a;
  a.resize(4);
}

对我来说打印出来:

 A::A(); this=0x7fcd634000e0
 A::A(); this=0x7fcd634000e4
 A::A(); this=0x7fcd634000e8
 A::A(); this=0x7fcd634000ec

如果您删除上面的移动构造函数并将其替换为已删除的复制构造函数,A 不再是 MoveInsertable/MoveConstructible 作为移动构造然后尝试使用已删除的复制构造函数,如 OP 中正确演示的那样问题。

【讨论】:

  • 但是请注意,libstdc++ 不尊重分配器模板参数;看我的回答。
  • 我有相同的输出,但仍然感到困惑,因为每个对象都调用了默认 ctor。所以 move ctor 似乎根本没有被调用。请注意,如果将移动 ctor 替换为复制 ctor,则结果保持不变:它永远不会被调用,但 std::vector::resize() 的实现需要它。
  • 我已在stackoverflow.com/a/12259886/576911stackoverflow.com/a/12259886/576911上投票支持 ecatmur 对您的问题的正确答案
  • 我怀疑只要避免调用resize(),没有默认的ctor就可以了。另请注意,如果计划使用矢量填充构造函数,则必须提供一个复制 ctor:std::vector&lt;A&gt; a(10,A());。
【解决方案2】:

在 C++11 中,要求取决于执行的操作。在std::vector&lt;T&gt;::resize()的情况下,T的要求是CopyInsertable进入向量。

来自§23.3.6.3

void resize(size_type sz);

....

要求:T 应为 CopyInsertable 到 *this。

【讨论】:

    【解决方案3】:

    On ideone,我看到一个对默认构造函数的调用。但是正在创建四个对象,其他对象必须以某种方式构建。实际上,原型对象是默认构造的,然后被复制了四次。

    C++11 标准(第 23.3.6.3 节)说将插入“值初始化”对象,但也要求类型是可复制的:

    void resize(size_type sz);

    • 效果:如果sz &lt;= size(),相当于erase(begin() + sz, end());。如果是size() &lt; sz,则将sz - size() 值初始化元素附加到序列中。
    • 要求:T 应为 CopyInsertable 到 *this。

    这里没有编译器错误;是你的代码错了。

    【讨论】:

    • Ben,有趣的是,我的输出与您在 ideone 上使用 gcc 4.5.4 得到的输出不同:我得到:A::A() on this=0x603010 A::A() on this=0x603014 A::A() on this=0x603018 A::A() on this=0x60301c,即默认 ctor 被调用 4 次,而不仅仅是一次然后是对象是从那里复制的。这是一个显着的差异......
    • 这可能是规格缺陷。请注意,仅采用大小的vector 构造函数只需要DefaultConstructible。 resize 没有理由要求 CopyInsertable;只有DefaultConstructible 以及可能需要的任何重新分配。
    • @NicolBolas 所以你是在暗示 gcc 有问题?
    • @Walter:规范缺陷不是软件中的错误。这是 spec 中的一个错误。
    【解决方案4】:

    要在向量中使用一个类,它应该有一个复制构造函数/赋值运算符或一个 noexcept 移动构造函数/赋值运算符。 GCC 不编译没有这些的示例是完全正确的。

    除了向量之外,你会如何做任何事情而不能复制或移动它所包含的内容?

    第一个示例有效的原因是,由于您没有定义任何复制或移动构造函数或赋值运算符,因此您获得了默认值。在第二个示例中,由于您显式删除了复制构造函数,因此您不会获得任何自动生成的构造函数或赋值运算符。

    【讨论】:

    • 这实际上是错误的。删除复制构造函数应该对赋值运算符没有影响,并且该类具有用户定义的默认构造函数。
    • +1 用于提及移动 ctor。确实,如果我给它(使用=default)但delete 复制ctor,它工作正确! 编辑我将您的赋值运算符重新解释为构造函数,以纠正您的错误。
    • @juanchopanza 删除复制 ctor 确实会阻止生成移动赋值运算符。由于遗留原因,它留下了复制赋值运算符,但任何使用它的程序都值得怀疑。
    • 但是您声称您没有获得自动生成的赋值运算符的说法是不正确的。你得到一个复制赋值运算符,不管它是否有意义。
    【解决方案5】:

    void resize(size_type) 需要CopyInsertable,这意味着分配器应该能够构造-复制类型:

    ::new((void*)p)A(A());
    

    这意味着需要一个复制构造函数。您应该可以使用自定义分配器绕过它:

    struct Allocator: public std::allocator<A> {
      void construct(A *, const A &) { }
    };
    

    但是 libstdc++ 不尊重这一点;见Should (in C++11) std::vector::resize(size_type) work for the default constructible value_type int[4]?

    【讨论】:

    • 为什么不用::new((void*)p)A(); 而不是::new((void*)p)A(A());,我不明白A(A()) 优于A() 的好处...
    • @PiotrNycz 来自 iterator_traits::construct 的默认实现,用于复制构造。
    猜你喜欢
    • 1970-01-01
    • 2017-04-10
    • 1970-01-01
    • 1970-01-01
    • 2015-07-14
    • 2011-05-14
    • 1970-01-01
    • 1970-01-01
    • 2019-10-12
    相关资源
    最近更新 更多