在不使用元素复制赋值运算符的情况下复制向量 - 可移植性

Copying vectors without using the element copy assignment operator - portability

本文关键字:复制 情况下 向量 可移植性 赋值运算符 元素      更新时间:2023-10-16

如果我有这样的类型:

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())

有没有办法在两者中都有效?

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

如果你有一个现有的向量y,你可以构造一个新的向量z然后用y交换它:

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

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

请注意,这可能会导致更大的内存使用量,因为 y 的任何现有内容都将保留,直到新交换的z超出范围。您可以尝试通过首先执行以下操作来缓解此问题

y.clear();
y.shrink_to_fit();

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

[现场演示]

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

自C++11以来,各个操作都有自己的要求。vector的相关要求是(来源:C++14表100(:

  • emplace_backMoveInsertibleMoveAssignable
  • insertCopyInsertableCopyAssignable
  • 构造函数: EmplaceConstructible

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

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

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


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

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

vector<int> y = x;

如果y已经存在,正如您的问题所暗示的那样,那么您几乎不走运:您不能使用任何插入函数,因此您只能修改现有元素。由于没有赋值运算符,因此无法通过赋值执行此操作。

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

考虑将移动

构造函数和移动赋值添加到类中,或者重新设计代码,以便不需要分配给y。(例如,使用指针(。

如果x.size() == y.size(),则可以使用放置新作为最后的手段:

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