不变问题和隐式移动构造函数

invariant probleme and implicit move constructor

本文关键字:移动 构造函数 问题      更新时间:2023-10-16

我读了这篇文章 cppnext 隐式移动,但我不明白这个问题:

#include <iostream>
#include <vector>
struct X
{
    // invariant: v.size() == 5
    X() : v(5) {}
    ~X()
    {
        std::cout << v[0] << std::endl;
    }
 private:    
    std::vector<int> v;
};
int main()
{
    std::vector<X> y;
    y.push_back(X()); // X() rvalue: copied in C++03, moved in C++0x
}

在MSVC2010下,运行时没有错误...谁能帮我?

这篇文章中有这样一句话:

这里的关键问题是,在 C++03 中,X 有一个不变性,即其 v 成员始终有 5 个元素。X::~X() 依靠该不变量,但是 新引入的移动构造函数从 V 移动,从而设置 它的长度为零。

我不明白为什么因为我们试图移动 X,v长度将为零

本文试图说明的问题是,当X在push_back中移动时,不变量被破坏了。临时X的向量v在移动后将为空,因此析构函数调用将调用未定义的行为。可能是因为编译器没有实现移动语义,或者因为未定义的行为不会导致任何可察觉的运行时错误。

您可以使用这个简单的程序检查此行为,我们在其中显式移动X实例:

#include <vector>
#include <iostream>
struct X {
  X() : v(5) {}
  std::vector<int> v;
};
int main() {
  X x0;
  std::cout << x0.v.size() << ", ";
  X x1 = std::move(x0);
  std::cout << x0.v.size() << "n";
}

在 GCC 4.7 上,这些产生

5, 0

这来自std::vector的移动构造函数,该构造函数由X的隐式生成构造函数使用。您可以直接检查std::vector

int main() {
  std::vector<int> v0(5);
  std::cout << v0.size() << ", ";
  std::vector<int> v1 = std::move(v0);
  std::cout << v0.size() << "n";
}

这将产生相同的输出。

现在,在您引用的示例中,用户定义的析构函数的存在意味着没有隐式生成的移动构造函数,因此X不会push_back中移动。

我不明白为什么因为我们尝试移动 X,v 长度将为零

好吧,首先,我们不会尝试在push_back调用中移动临时X()。我们确实移动了它,或者我们不移动[*]。

如果我们要移动它(使用隐式生成的移动构造函数),则移动其数据成员v。这篇文章的假设是,从向量移动会让它为空,尽管我不记得这是必需的还是典型的。但它确实会让v处于无法保证您可以访问其以前元素的状态。

请记住,移动的全部意义在于它从源传输昂贵的复制资源。因此,临时对象不再保证具有其包含 5 个元素的内部数组,因为移动的目的地已经采取了它。

至于为什么它在 MSVC 2010 中工作 - 请记住,此代码在文章中作为"调整 #1 - 析构函数抑制隐式移动"上方的示例提供。C++11 实际上确实包含此调整 - 如果存在用户定义的析构函数 (12.8/9),则不会生成隐式移动构造函数。所以临时的不是移动的,而是复制的。

[*] 移动或不移动,没有尝试。是否移动它不取决于实现,标准对此进行了定义。

MSVC2010没有

实现auto=generated move构造函数,所以即使你的代码会生成一个,你也不会在那里看到它。因此,与往常一样,"随机实现X做了Y"是一个毫无意义的陈述。

> 在VS2010中,临时将被复制,然后销毁。使用移动语义,临时对象将仅用于向量。