自动生成移动构造函数导致非法行为 [英] Autogenerated move constructors causing illegal behavior

查看:130
本文介绍了自动生成移动构造函数导致非法行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我问有关移动构造函数的问题,我尚未接受答案,因为我感觉更多混淆了问题的某些方面,即使我开始抓住别人。特别是,我发现一个令人惊讶的情况,其中g ++和clang ++生成不正确的移动构造函数。



问题总结




  • g ++和clang ++明显违反了在明确定义析构函数时不会生成移动构造函数的规则;为什么?这是一个错误,还是我误解了发生了什么?

  • 为了正确,这些(可能是非法的)移动构造函数应该使RHS指针成员无效,但他们不会。为什么不呢?

  • 看来,避免不必要行为的唯一方法是为使用正确移动构造函数> delete 在它的析构函数中。



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



考虑下面的代码:

  class NoMove 
{
public:
〜NoMove(){}
};
int main()
{
std :: cout< NoMove move-constructible?<
std :: is_move_constructible< NoMove> :: value<< std :: endl;
}

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

  NoMove移动构造? 1 

...但由于 NoMove 一个明确定义的析构函数,我希望既不移动构造函数也不应该自动生成复制构造函数。注意,意外的构造函数生成不是由于析构函数是微不足道的事实;我得到相同的行为,当析构函数 delete [] s数组(!!),我甚至能够编译需要 a有效移动构造函数(!!!!!)。 (见下面的例子)这里发生了什么?



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



涉及 delete 时提供安全移动构造函数是相当简单的,但我只是想确保我明白:当一个类包含一个指针成员和拥有底层数据,是有



考虑下面的示例,它类似于上面的 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;
// ...做一些编组...
DataType marshalled_data {std :: move(marshaller).toDataType()};
}

这样编译就好了 - 显示 DataType 有一个自动生成的移动构造函数。当然,当运行时,它会导致双重删除错误。



现在,这将是好的,如果自动生成移动构造函数使RHS指针无效。因此,如果可以在这里自动生成移动构造函数,为什么不能安全地进行呢? ?使此工作的移动构造函数简单:

  DataType(DataType&& rhs):
val {rhs .val}
{
rhs.val = nullptr;
}

(对吗?我错过了什么? c> val {std :: move(rhs.val)} ?)



这似乎是一个完全安全的函数auto -生成;编译器知道 rhs 是一个r值,因为函数原型如此说明,因此修改它是完全可以接受的。因此,即使 DataType 的析构函数没有 delete [] val 像在自动生成的版本中不会有任何原因 无效 rhs ,除非我认为,



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



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



最后,一个(希望)容易的问题:做Qt 5.4的堆分配类,如 QByteArray 我实际上使用作为 DataType 在我原来的问题)已正确实现移动构造函数,使所有移动的拥有指针无效?



我甚至不会问,因为Qt看起来很坚实,我还没有看到任何双重删除错误 ,但考虑到我被关掉了通过这些不正确的编译器生成的移动构造函数,我担心这是很容易结束了不正确的移动构造函数在一个否则良好实现的库。



相关地,那么在没有显式移动构造函数的 C ++ 11 之前编写的Qt库呢?如果我可以不小心强制在这种情况下行为错误的自动生成的移动构造函数,有没有人知道编译,比如说,与符合C ++ 11的编译器Qt 3会导致未定义的破坏行为在这样的用例? is_move_constructible 和有一个移动构造函数。 is_move_constructible< T> 不测试T是否有移动构造函数。它测试 T 是否可以从类型 T 的右值构造。 const T& 可以绑定到 T 右值。



您看到的是自动生成的 构造函数 T(const T&)执行其工作 - 并且惨败。


我希望既不会自动生成move构造函数也不会自动生成复制构造函数。


< blockquote>

你的链接谈论移动构造函数。它不涉及复制构造函数,如果你不声明它,它总是隐式声明。



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


如果类定义没有显式声明一个副本
构造函数, 。如果类定义
声明了一个移动构造函数或移动赋值运算符,则
隐式声明的拷贝构造函数定义为deleted;否则,
它被定义为默认值(8.4)。如果
类具有用户声明的复制赋值运算符或用户声明的
析构函数,后一种情况将被弃用。



I asked a question about move constructors for which I haven't accepted an answer yet because I'm feeling more confused about certain aspects of the question even as I'm starting to get a grip on others. In particular, I've found a surprising case in which both g++ and clang++ generate incorrect move-constructors.

Question summary

  • g++ and clang++ apparently violate the rule that move-constructors are not generated when destructors are explicitly defined; why? Is this a bug, or am I misunderstanding what's going on?
  • For correctness, these (possibly-illegal) move constructors should invalidate RHS pointer members, but they don't. Why not?
  • It appears that the only way to avoid the unwanted behavior is to explicitly define a correct move constructor for every class that uses delete in its destructor. Does the Qt library (version 5.4) do this?

Part 1: Illegally auto-generated constructors?

Consider the following code:

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

Compiled with both g++ 4.9.2 and clang++ 3.5.1, this code prints:

NoMove move-constructible? 1

...But since NoMove has an explicitly defined destructor, I would expect that neither a move constructor nor a copy constructor should be auto-generated. Note that the unexpected constructor generation is not due to the fact that the destructor is trivial; I get the same behavior when the destructor delete[]s an array (!!), and I am even able to compile code that requires a valid move constructor (!!!!!). (See example below.) What's going on here? Is it legal to auto-generate a move constructor here, and if so, why?

Part 2: (Possibly illegal) auto-generated constructors causing undefined behavior?

It appears that providing safe move constructors when delete is involved is fairly simple, but I just want to make sure I understand: when a class contains a pointer member and owns the underlying data, is there any case in which it wouldn't be correct and sufficient for the move constructor to invalidate the RHS pointer after setting the destination pointer to the old value?

Consider the following example, which is similar to the NoMove example above and is based on my original question:

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()};
}

This compiles just fine--showing that, yes, DataType has an auto-generated move constructor. And of course, when run, it causes a double-deletion error.

Now, this would be okay, if the auto-generated move constructor invalidated the RHS pointer. So, if it's okay to auto-generate a move constructor here, why isn't that done safely? The move constructor that makes this work is simply:

DataType(DataType&& rhs) :
  val{rhs.val}
{
  rhs.val = nullptr;
}

(Right? Am I missing anything? Should it perhaps be val{std::move(rhs.val)}?)

This seems like it would be a perfectly safe function to auto-generate; the compiler knows that rhs is an r-value because the function prototype says so, and therefore it's entirely acceptable to modify it. So even if DataType's destructor didn't delete[] val, it seems like there wouldn't be any reason not to invalidate rhs in the auto-generated version, except, I suppose, for the fact that this leads to a trivial performance hit.

So if the compiler is auto-generating this method--which, again, it shouldn't, especially since we can just as easily get this exact behavior from standard library code using unique_ptr-- why is it auto-generating it incorrectly?

Part 3: Avoiding this behavior in Qt (especially QByteArray in Qt 5.4)

Finally, a (hopefully) easy question: do Qt 5.4's heap-allocating classes such as QByteArray (which is what I'm actually using as the DataType in my original question) have correctly implemented move constructors, invalidating any moved-from owning pointer(s)?

I wouldn't even bother to ask, because Qt seems pretty solid and I haven't seen any double-deletion errors yet, but given that I was taken off guard by these incorrect compiler-generated move constructors, I'm concerned that it's quite easy to end up with incorrect move constructors in an otherwise-well-implemented library.

Relatedly, what about Qt libraries written before C++11 that don't have explicit move-constructors? If I can accidentally coerce an auto-generated move constructor that behaves erroneously in this case, does anyone know if compiling, say, Qt 3 with a C++11-compliant compiler causes undefined destruction behavior in use-cases like this?

解决方案

The problem is that you are confusing is_move_constructible and "has a move constructor". is_move_constructible<T> doesn't test whether T has a move constructor. It tests whether T can be constructed from an rvalue of type T. And const T& can bind to a T rvalue.

What you are seeing is the autogenerated copy constructor T(const T&) doing its work - and failing miserably.

I would expect that neither a move constructor nor a copy constructor should be auto-generated.

Your link talks about the move constructor. It doesn't talk about the copy constructor, which is always implicitly declared if you don't declare it.

Now, if you declared a move operation, the implicitly declared copy constructor would be defined as deleted, but you didn't do that, so it's defined as defaulted and performs a memberwise copy. [class.copy]/p7:

If the class definition does not explicitly declare a copy constructor, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted (8.4). The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor.

这篇关于自动生成移动构造函数导致非法行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆