不使用元素复制赋值运算符复制向量 - 可移植性

Copying vectors without using the element copy assignment operator - portability

如果我有这样的类型:

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

    ...
private:
    ...
};

我有两个这种类型的向量:

std::vector<Foo> x;
std::vector<Foo> y;

我想把x的内容复制到y,请问有跨平台的方法吗?

VC++ 将使用 y.assign(x.begin(), x.end()) 执行此操作,它使用 Foo 的复制构造函数而不是已删除的复制赋值运算符。但是 GCC 抱怨缺少复制赋值运算符,无论您尝试 y = x 还是 y.assign(x.begin(), x.end()).

有什么方法可以同时适用于两者吗?

在 C++11 之前,您的代码是不可能的:向量元素类型必须是 CopyAssignable,这意味着可复制构造和可分配。请注意,如果类型不满足这些要求,则代码格式错误,无需诊断;这给了编译器接受或拒绝它的自由度。

从 C++11 开始,各个操作都有自己的要求。 vector的相关要求是(来源:C++14 Table 100):

  • emplace_back : MoveInsertibleMoveAssignable
  • insertCopyInsertableCopyAssignable
  • 构造函数:EmplaceConstructible

这些特征的含义(大致)是:

  • CopyInsertable: 有拷贝构造函数
  • MoveInsertable: 有复制构造函数或移动构造函数(或两者)
  • CopyAssignable: 有复制构造函数和复制赋值运算符
  • MoveAssignableMoveInsertable,并且有复制赋值或移动赋值运算符(或两者)
  • EmplaceConstructible: 有构造函数

这里的基本原理是任何插入都可能导致内存重新分配,这要求您的对象能够被复制或移动。但是,标准不要求编译器测试构造函数或赋值运算符是否都可用,如果另一个不可用则使用一个。相反,它指定两者都必须可用。


您没有指定您的 class 是否可移动。假设不是,那么这意味着您不能使用任何 emplace 或 insert 方法,也不能使用任何其他可能导致重新分配的函数。

如果你当时正在创建 y 那么你当然可以使用初始化:

vector<int> y = x;

如果 y 已经存在,正如您的问题所暗示的那样,那么您就差点倒霉了:您不能使用任何插入函数,所以您只能修改现有元素。由于没有赋值运算符,您不能通过赋值来完成此操作。

但是您可以按照安德鲁的回答中的建议使用y.swap()

考虑向您的 class 添加移动构造函数和移动赋值,或者重新设计您的代码,这样您就不需要向 y 赋值了。 (例如使用指针)。

如果 x.size() == y.size() 那么您可以使用 placement new 作为最后一招:

for (size_t i = 0; i != y.size(); ++i)
{
    y[i].~Foo();
    new(&y[i]) Foo(x[i]);    // copy-construct
}

标准要求 y.assign(it1, it2) 工作,元素类型 TCopyAssignable。也就是说,可复制构造和可赋值。您的类型不可分配,因此您不能依赖 assigny = x 也是如此。标准的第 23.2.3 节描述了各种序列容器操作的要求。

如果您有一个现有向量 y,您可以构建一个新向量 z 然后将其与 y:

交换
{
  std::vector<Foo> z(x.begin(), x.end());
  z.swap(y);
}

这里使用范围构造函数,只需要TEmplaceConstructible,不需要赋值运算符!然后您可以交换底层内存而无需任何其他副本。

请注意,这可能会导致更大的内存使用量,因为 y 的任何现有内容将持续存在,直到新交换的 z 超出范围。您可以尝试通过先做

来缓解这种情况
y.clear();
y.shrink_to_fit();

尽管 shrink_to_fit() 只是一个请求,您的库实现可能不会兑现。

[Live demo]