insert
,push_back
和emplace
(_back
)可能导致重新分配std::vector
。在重新分配容器时,我感到困惑的是看到以下代码复制元素而不是移动它们。
#include <iostream>
#include <vector>
struct foo {
int value;
explicit foo(int value) : value(value) {
std::cout << "foo(" << value << ")\n";
}
foo(foo const& other) noexcept : value(other.value) {
std::cout << "foo(foo(" << value << "))\n";
}
foo(foo&& other) noexcept : value(std::move(other.value)) {
other.value = -1;
std::cout << "foo(move(foo(" << value << "))\n";
}
~foo() {
if (value != -1)
std::cout << "~foo(" << value << ")\n";
}
};
int main() {
std::vector<foo> foos;
foos.emplace_back(1);
foos.emplace_back(2);
}
在我使用特定编译器(GCC 4.7)的特定计算机上,将打印以下内容:
foo(1)
foo(2)
foo(foo(1))
~foo(1)
~foo(1)
~foo(2)
但是,删除复制构造函数(foo(foo const&) = delete;
)时,会生成以下(预期)输出:
foo(1)
foo(2)
foo(move(foo(1))
~foo(1)
~foo(2)
为什么?移动通常不会比复制更有效率,或者至少效率不高吗?
值得注意的是GCC 4.5.1 does the expected thing - 这是GCC 4.7中的回归还是一些非常聪明的优化,因为编译器发现我的对象复制起来很便宜(但是怎么样?!)?
另请注意,我通过实验性地在插入前放置foos.reserve(2);
来确保重新分配导致此 ;这导致既不复制也不移动。
答案 0 :(得分:12)
简短的回答是我认为@BenVoigt基本上是正确的。
在reserve
(§23.3.6.3/ 2)的描述中,它说:
如果除了非CopyInsertable类型的move构造函数之外抛出异常,则没有效果。
[§23.3.6.3/ 12中对resize
的描述要求相同。]
这意味着如果T是CopyInsertable,则会获得强大的异常安全性。为了确保,如果它推断(通过未指定的方式)移动构造永远不会抛出,它仅可以使用移动构造。但是,无法保证throw()
或noexcept
对此有必要或充分。如果T是CopyInsertable,它可以只选择始终使用复制构造。基本上,正在发生的是标准需要复制构造式语义;编译器只能在as-if规则下使用move构造,并且可以自由定义何时或是否会运用该选项。
如果T不是CopyInsertable,重新分配将使用移动构造,但异常安全取决于T的移动构造函数是否可以抛出。如果它没有抛出,你就会获得强大的异常安全性,但是如果它抛出,你就不会(我认为你可能得到了基本的保证,但也许甚至没有,绝对没有)。
答案 1 :(得分:7)
这不是回归,而是修复错误。该标准指定std :: vector只会优先选择非投掷的元素移动构造函数。
另请参阅this explanation和this bug report。
This question也很重要。
答案 2 :(得分:7)
Tip-of-trunk clang + libc ++ gets:
foo(1)
foo(2)
foo(move(foo(1))
~foo(2)
~foo(1)
如果从移动构造函数中删除noexcept
,则会获得复制解决方案:
foo(1)
foo(2)
foo(foo(1))
~foo(1)
~foo(2)
~foo(1)