为什么 std::vector 的元素需要移动构造函数?
Why does std::vector require move-constructors for its elements?
C++98 声明 std::vector 元素应该有复制构造函数。在 C++11 中,情况不再如此。相反,元素必须具有移动构造函数。
根据您使用 std::vector 执行的操作,您可能真的需要也可能不需要调用复制构造函数或移动构造函数,但标准始终只正式要求调用其中之一。为什么?
更新: 显然,前提是不正确的。我的困惑源于阅读 this.
等答案
在 C++11 中声明元素必须具有 std::vector 的移动构造函数的位置在哪里?这样的规则会破坏很多现有代码,因为现有 类 不一定具有移动构造函数。您可能确实需要复制构造函数,或者如果您没有复制构造函数,那么您至少需要有一个移动构造函数。
正如评论中所暗示的那样,对存储在向量中的所有对象并没有简单的一揽子要求。而是把要求放在具体的操作上。
尽管许多操作do要求对象类型是MoveConstructible
and/orMoveAssignable
,复制构造和复制赋值符合满足该要求——也就是说,复制构造基本上是移动构造的超集和(同样复制赋值与移动赋值)。
反之则不然。例如,如果您对向量使用两个参数的构造函数:
std::vector<T> vec(n, t);
...那么 T
必须是可复制构造的(因为它将尝试在向量中创建 t
的 n
副本,并且使用移动构造,它只会能够创建一个项)。
如果你想把它看成一个继承层次,你可以把copy(assignment|construction)看作派生的class,而move(assignment|construction)作为基础class,因此您可以隐式地用复制代替移动,但反之则不行。
在很多情况下,对 list
的元素的要求比对 vector
或 deque
的元素的要求更宽松(对 [=17= 的元素的要求) ] 和 deque
几乎总是相同的,我知道的唯一例外(感谢@yakk)是 emplace_back
,它需要向量的 MoveConstructible,否则需要 EmplaceConstructible 和下面列出的下一个项目——但是注意它的警告)。
如果您使用 std::vector<x> myvector(i, j);
或 myvector.assign(i, j);
,(其中 i
和 j
是迭代器)那么对 T 的要求取决于 class迭代器——如果它们是前向迭代器,那么 T 只需要是 EmplaceConstructible,否则 T 必须是 MoveConstructible。警告:虽然这确实反映了标准中的当前措辞,但根据 LWG 2266 对构造函数的限制是不正确的:它们应该适用于任何迭代器类别,并且同样适用于 vector 和 deque。该标准的某些未来版本将反映这一点,但在实际使用中已经是这样了。
如果有人关心为什么会这样,以及为什么要对非前向迭代器进行特殊处理:问题是对于 std::istream_iterator
之类的东西,无法提前知道有多少项迭代器范围可能会引用。对于随机访问迭代器之类的东西,它可以简单地使用 j - i
来确定项目的数量,使之成为 space,然后适当地插入。为了高效地使用 istream_iterator
s,他们通常将数据复制到当前集合的末尾,然后在整个范围 copied/moved 进入集合后,使用 rotate
将它们移动到所需的位置。额外要求支持使用 rotate
。
insert
有一个变体,如果您要插入到 vector
或 deque
(这里的推理与上面给出的推理类似:对于insert
的这种变体,元素实际上是插入一个地方,然后四处移动以使它们到达它们所属的位置)。
C++98 声明 std::vector 元素应该有复制构造函数。在 C++11 中,情况不再如此。相反,元素必须具有移动构造函数。
根据您使用 std::vector 执行的操作,您可能真的需要也可能不需要调用复制构造函数或移动构造函数,但标准始终只正式要求调用其中之一。为什么?
更新: 显然,前提是不正确的。我的困惑源于阅读 this.
等答案在 C++11 中声明元素必须具有 std::vector 的移动构造函数的位置在哪里?这样的规则会破坏很多现有代码,因为现有 类 不一定具有移动构造函数。您可能确实需要复制构造函数,或者如果您没有复制构造函数,那么您至少需要有一个移动构造函数。
正如评论中所暗示的那样,对存储在向量中的所有对象并没有简单的一揽子要求。而是把要求放在具体的操作上。
尽管许多操作do要求对象类型是MoveConstructible
and/orMoveAssignable
,复制构造和复制赋值符合满足该要求——也就是说,复制构造基本上是移动构造的超集和(同样复制赋值与移动赋值)。
反之则不然。例如,如果您对向量使用两个参数的构造函数:
std::vector<T> vec(n, t);
...那么 T
必须是可复制构造的(因为它将尝试在向量中创建 t
的 n
副本,并且使用移动构造,它只会能够创建一个项)。
如果你想把它看成一个继承层次,你可以把copy(assignment|construction)看作派生的class,而move(assignment|construction)作为基础class,因此您可以隐式地用复制代替移动,但反之则不行。
在很多情况下,对 list
的元素的要求比对 vector
或 deque
的元素的要求更宽松(对 [=17= 的元素的要求) ] 和 deque
几乎总是相同的,我知道的唯一例外(感谢@yakk)是 emplace_back
,它需要向量的 MoveConstructible,否则需要 EmplaceConstructible 和下面列出的下一个项目——但是注意它的警告)。
如果您使用 std::vector<x> myvector(i, j);
或 myvector.assign(i, j);
,(其中 i
和 j
是迭代器)那么对 T 的要求取决于 class迭代器——如果它们是前向迭代器,那么 T 只需要是 EmplaceConstructible,否则 T 必须是 MoveConstructible。警告:虽然这确实反映了标准中的当前措辞,但根据 LWG 2266 对构造函数的限制是不正确的:它们应该适用于任何迭代器类别,并且同样适用于 vector 和 deque。该标准的某些未来版本将反映这一点,但在实际使用中已经是这样了。
如果有人关心为什么会这样,以及为什么要对非前向迭代器进行特殊处理:问题是对于 std::istream_iterator
之类的东西,无法提前知道有多少项迭代器范围可能会引用。对于随机访问迭代器之类的东西,它可以简单地使用 j - i
来确定项目的数量,使之成为 space,然后适当地插入。为了高效地使用 istream_iterator
s,他们通常将数据复制到当前集合的末尾,然后在整个范围 copied/moved 进入集合后,使用 rotate
将它们移动到所需的位置。额外要求支持使用 rotate
。
insert
有一个变体,如果您要插入到 vector
或 deque
(这里的推理与上面给出的推理类似:对于insert
的这种变体,元素实际上是插入一个地方,然后四处移动以使它们到达它们所属的位置)。