自动生成的移动构造函数导致非法行为

Kyl*_*and 6 c++ qt pointers move-semantics c++11

我问了一个关于移动构造函数的问题,我尚未接受答案,因为即使我开始掌握其他问题,我对问题的某些方面感到更加困惑.特别是,我发现了一个令人惊讶的案例,其中g ++和clang ++都生成了错误的移动构造函数.

问题摘要

  • g ++和clang ++显然违反了在明确定义析构函数时不生成move-constructors的规则; 为什么?这是一个错误,还是我误解了发生了什么?
  • 为了正确,这些(可能是非法的)移动构造函数应该使RHS指针成员无效,但它们不会.为什么不?
  • 似乎避免不需要的行为的唯一方法是为在析构函数中使用的每个类显式定义一个正确的移动构造delete函数.Qt库(版本5.4)是否这样做?

第1部分:非法自动生成的构造函数?

请考虑以下代码:

class NoMove
{
  public:
    ~NoMove() {}
};
int main()
{
  std::cout << "NoMove move-constructible? " <<
    std::is_move_constructible<NoMove>::value << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

使用g++4.9.2和clang++3.5.1 编译,此代码打印:

NoMove move-constructible? 1
Run Code Online (Sandbox Code Playgroud)

...但是由于NoMove有一个明确定义的析构函数,我希望应该自动生成移动构造函数和复制构造函数.请注意,意外的构造函数生成不是因为析构函数是微不足道的; 当析构函数delete[]是一个数组(!!)时,我得到了相同的行为,我甚至能够编译需要有效移动构造函数的代码(!!!!!).(见下面的例子.)这里发生了什么?在这里自动生成移动构造函数是否合法,如果是,为什么?

第2部分:(可能是非法的)自动生成的构造函数导致未定义的行为?

看来,提供安全的移动构造函数时delete涉及相当简单,但我只是想确保我的理解:当一个类包含一个指向成员,拥有基本数据,有没有任何在它是不正确和案例在将目标指针设置为旧值后,移动构造函数是否足以使RHS指针无效?

请考虑以下示例,该NoMove示例与上面的示例类似,并基于我原来的问题:

class DataType
{
  public:
    DataType()
    {
      val = new int[35];
    }
    ~DataType()
    {
      delete[] val;
    }
  private:
    int* val;
};

class Marshaller
{
  public:
    Marshaller()=default;
    DataType toDataType() &&
    {
      return std::move(data);
    }
  private:
    DataType data;
};

void DoMarshalling()
{
  Marshaller marshaller;
  // ... do some marshalling...
  DataType marshalled_data{std::move(marshaller).toDataType()};
}
Run Code Online (Sandbox Code Playgroud)

这编译得很好 - 显示,是的,DataType有一个自动生成的移动构造函数.当然,运行时会导致双删除错误.

现在,如果自动生成的移动构造函数使RHS指针无效,这也没关系.那么,如果可以在这里自动生成移动构造函数,为什么不能安全地完成?使这项工作的移动构造函数很简单:

DataType(DataType&& rhs) :
  val{rhs.val}
{
  rhs.val = nullptr;
}
Run Code Online (Sandbox Code Playgroud)

(对吗?我错过了什么?它应该是val{std::move(rhs.val)}吗?)

这似乎是一个完全安全的自动生成功能; 编译器知道rhs是一个r值,因为函数原型是这样说的,因此修改它是完全可以接受的.因此,即使DataType析构函数没有 delete[] val,似乎也没有任何理由不在rhs自动生成的版本中失效,除了我想,因为这会导致一个微不足道的性能损失.

因此,如果编译器是自动生成此方法 - 同样,它不应该,特别是因为我们可以使用标准库代码轻松地获得这种确切的行为unique_ptr- 为什么它会错误地自动生成它?

第3部分:在Qt中避免这种行为(特别是QByteArray在Qt 5.4中)

最后,一个(希望)简单的问题:做Qt 5.4的堆分配类(例如QByteArray我在实际DataType问题中实际使用的那个)已经正确实现了移动构造函数,使任何移动的拥有指针无效( S)?

我不会甚至懒得问,因为Qt的似乎很扎实,我还没有看到任何的双缺失的错误还没有,但考虑到我对这些不正确的编译器生成的举动构造带下后卫,我担心它的很容易在一个实现良好的库中以不正确的移动构造函数结束.

相关地,之前编写的Qt库C++11没有明确的move-constructors呢?如果我不小心强制在这种情况下行为错误的自动生成的移动构造函数,有人知道是否编译Qt 3与C++ 11兼容的编译器导致在这样的用例中的未定义的破坏行为?

T.C*_*.C. 7

问题是你很困惑is_move_constructible并且"有一个移动构造函数".is_move_constructible<T>不测试T是否有移动构造函数.它测试是否T可以从类型的右值构造T.并且const T&可以绑定到T右值.

您所看到的是自动生成的复制构造函数T(const T&)正在执行其工作 - 并且失败了.

我希望自动生成移动构造函数和复制构造函数.

您的链接谈论移动构造函数.它没有讨论复制构造函数,如果你没有声明它,它总是被隐式声明.

现在,如果您声明了一个移动操作,隐式声明的复制构造函数将被定义为已删除,但您没有这样做,因此它被定义为默认并执行成员复制.[class.copy]/P7:

如果类定义没有显式声明复制构造函数,则会隐式声明一个.如果类定义声明了移动构造函数或移动赋值运算符,则隐式声明的复制构造函数被定义为已删除; 否则,它被定义为默认值(8.4).如果类具有用户声明的复制赋值运算符或用户声明的析构函数,则不推荐使用后一种情况.