不使用元素复制赋值运算符复制向量 - 可移植性
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
: MoveInsertible
和 MoveAssignable
insert
:CopyInsertable
和 CopyAssignable
- 构造函数:
EmplaceConstructible
这些特征的含义(大致)是:
CopyInsertable
: 有拷贝构造函数
MoveInsertable
: 有复制构造函数或移动构造函数(或两者)
CopyAssignable
: 有复制构造函数和复制赋值运算符
MoveAssignable
:MoveInsertable
,并且有复制赋值或移动赋值运算符(或两者)
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)
工作,元素类型 T
是 CopyAssignable。也就是说,可复制构造和可赋值。您的类型不可分配,因此您不能依赖 assign
。 y = 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()
只是一个请求,您的库实现可能不会兑现。
如果我有这样的类型:
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
:MoveInsertible
和MoveAssignable
insert
:CopyInsertable
和CopyAssignable
- 构造函数:
EmplaceConstructible
这些特征的含义(大致)是:
CopyInsertable
: 有拷贝构造函数MoveInsertable
: 有复制构造函数或移动构造函数(或两者)CopyAssignable
: 有复制构造函数和复制赋值运算符MoveAssignable
:MoveInsertable
,并且有复制赋值或移动赋值运算符(或两者)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)
工作,元素类型 T
是 CopyAssignable。也就是说,可复制构造和可赋值。您的类型不可分配,因此您不能依赖 assign
。 y = 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()
只是一个请求,您的库实现可能不会兑现。