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. To that end, I'm asking this, which is a three-part question for which, hopefully, all the elements are related enough to make it feel coherent.
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?
- How can explicit move-semantics be safely provided for classes involving pointer members? Is invalidating the RHS pointers sufficient?
- If so, why don't auto-generated move-constructors for simple classes invalidate moved-from pointers in the RHS?
- Does Qt's
QByteArrayprovide a move-constructor that prevents double deletion of the underlying data?
Q1: 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 this 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?
Q2: Safely providing explicit move-constructors for classes containing pointers
This seems like a basic question that should be provided in a tutorial somewhere, and in fact there's an answered SO question focusing on it, but I just want to make sure I understand: when a class contains a pointer member and owns the underlying data, how can a safe move-constructor be written? Is there any case in which it wouldn't be correct and sufficient 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.
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?
Q3: Qt's QByteArray
Finally, a (hopefully) easy question: do Qt 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.
Aucun commentaire:
Enregistrer un commentaire