lundi 3 avril 2017

How to avoid move semantics for class members?

Example:
I have the following code (reduced to model example, Qt library used, Qt classes behavior explained below):

struct Test_impl {
  int x;
  Test_impl() : x(0) {}
  Test_impl(int val) : x(val) {}
};

class Test {
  QSharedPointer<Test_impl> m;
public:
  Test() : m(new Test_impl()) {}
  Test(int val) : m(new Test_impl(val)) {}
  void assign(const QVariant& v) {m = v.value<Test>().m; ++m->x;}
  ~Test(){--m->x;}
};

Q_DECLARE_METATYPE(Test)

QSharedPointer is a smart pointer implementing move semantic (which is omitted in documentation). QVariant is somewhat similar to std::any and has the template method

template<typename T> inline T value() const;

Macro Q_DECLARE_METATYPE allows values of type Test to be placed inside a QVariant.

Problem:
Line m = v.value<Test>().m; seemingly invokes move assignment for the field m of a temporary object returned by value(). After that, Test destructor is called and promptly crashes trying to access illegal address.
Generally, the problem as I see it is that while move assignment leaves the object itself in a consistent state, state of an object containing moved entity "unexpectedly" changes.

There are several ways to avoid the problem in this particular example I can think of: change Test destructor to expect null m or explicitly create temporary Test object in assign (MS Visual Studio allows to write std::swap(m, v.value<Test>().m), but this code is illegal).

Question(s):
Is there a "proper" (best practice?) way to explicitly invoke copy assignment instead of move? Is there a way to prohibit move semantics for class fields used in the destructor? Why moving a temporary object member was made a default option in the first place?

Aucun commentaire:

Enregistrer un commentaire