在一个变量中使用std :: move之后,该变量可能是类中的一个字段,如:
class A {
public:
vector<string>&& stealVector() {
return std::move(myVector);
}
void recreateMyVector() {
}
private:
vector<string> myVector;
};
Run Code Online (Sandbox Code Playgroud)
我如何重新创建矢量,就像一个清晰的矢量?在std :: move之后myVector中还剩下什么?
Ker*_* SB 15
常见的咒语是已经"移动"的变量处于有效但未指定的状态.这意味着可以销毁并分配给变量,但没有别的.
(Stepanov称之为"部分形成",我相信,这是一个很好的术语.)
要明确的是,这不是一个严格的规则; 相反,它是关于如何考虑移动的指导原则:在你离开之后,你不应该再想要使用原始对象了.任何尝试与原始对象做一些非平凡的事情(除了分配或破坏它)都应该仔细考虑和证明.
但是,在每种特定情况下,可能存在对移动对象有意义的其他操作,并且您可能希望利用这些操作.例如:
标准库容器描述了它们的操作的前提条件; 没有先决条件的操作都没问题.想到的唯一有用的是clear(),也许swap()(但更喜欢分配而不是交换).还有其他没有先决条件的操作,例如size(),但是按照上述推理,你不应该在你刚才说不再需要的对象大小之后进行任何业务查询.
unique_ptr<T, D>移动后的保证,它是null,您可以在有条件地拥有所有权的情况下利用它:
std::unique_ptr<T> resource(new T);
std::vector<std::function<int(std::unique_ptr<T> &)> handlers = /* ... */;
for (auto const & f : handlers)
{
int result = f(resource);
if (!resource) { return result; }
}
Run Code Online (Sandbox Code Playgroud)
处理程序如下所示:
int foo_handler(std::unique_ptr<T> & p)
{
if (some_condition))
{
another_container.remember(std::move(p));
return another_container.state();
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
通常可能让处理程序返回一些其他类型的状态,指示它是否从唯一指针获取所有权,但由于标准实际上保证从一个唯一指针移动将其保留为null,我们可以利用它在唯一指针本身传输该信息.
将成员向量移动到本地向量,清除成员,按值返回本地.
std::vector<string> stealVector() {
auto ret = std::move(myVector);
myVector.clear();
return ret;
}
Run Code Online (Sandbox Code Playgroud)
由于我觉得到目前为止,斯捷潘诺夫的答案被歪曲了,所以让我添加一个我自己的快速概述:
对于std类型(并且仅对于这些类型),标准指定移出的对象保留在著名的“有效但未指定”状态。特别是,没有一种std类型使用斯捷潘诺夫的部分形成状态,包括我在内的一些人认为这是一个错误。
对于您自己的类型,您应该努力使用默认构造函数以及移动的源对象来建立部分形成的状态,Stepanov 在《编程原理》(2009)中将其定义为一种状态,其中唯一有效的操作是破坏并分配新值。特别是,部分形成的状态不需要表示对象的有效值,也不需要遵守正常的类不变量。
与普遍看法相反,这并不是什么新鲜事。自 C/C++ 诞生以来,部分形成状态就存在:
int i; // i is Partially-Formed: only going out of scope and
// assignment are allowed, and compilers understand this!
Run Code Online (Sandbox Code Playgroud)
对于用户来说,这实际上意味着永远不要假设您可以对移出的对象执行更多操作,而不是销毁它或为其分配新值,当然,除非文档说明您可以执行更多操作,这通常是可能的对于容器来说,它通常可以自然而有效地建立空状态。
对于类作者来说,这意味着你有两种选择:
首先,您可以像 STL 那样避免部分形成的状态。但是对于具有远程状态的类,例如 pimpl'ed 类,这意味着要表示一个有效值,您要么接受nullptr的有效值pImpl,提示您在公共 API 级别定义 a 的含义nullptr pImpl,包括。检查nullptr所有成员函数。
或者,您需要pImpl为移出的(和默认构造的)对象分配一个新对象,当然,任何注重性能的 C++ 程序员都不会这样做。然而,注重性能的 C++ 程序员也不希望nullptr仅仅为了支持移出对象的重要使用的次要用例而在代码中乱扔检查。
这给我们带来了第二种选择:拥抱部分形成的国家。这意味着,您接受nullptr pImpl,但仅限于默认构造和移出的对象。Anullptr pImpl代表部分形成状态,其中只允许销毁和分配另一个值。这意味着只有 dtor 和赋值运算符需要能够处理 a nullptr pImpl,而所有其他成员都可以假定有效的pImpl。这还有另一个好处:您的默认构造函数和移动运算符都可以是noexcept,这对于在中使用很重要std::vector(因此在重新分配时使用移动而不是副本)。
示例Pen类:
class Pen {
struct Private;
Private *pImpl = nullptr;
public:
Pen() noexcept = default;
Pen(Pen &&other) noexcept : pImpl{std::exchange(other.pImpl, {})} {}
Pen(const Pen &other) : pImpl{new Private{*other.pImpl}} {} // assumes valid `other`
Pen &operator=(Pen &&other) noexcept {
Pen(std::move(other)).swap(*this);
return *this;
}
Pen &operator=(const Pen &other) {
Pen(other).swap(*this);
return *this;
}
void swap(Pen &other) noexcept {
using std::swap;
swap(pImpl, other.pImpl);
}
int width() const { return pImpl->width; }
// ...
};
Run Code Online (Sandbox Code Playgroud)