This is a very convoluted example that is probably rare, but I ran into problems when doing the following:
class MyClass{
public:
SubClass1 object1; //has pointers/smart-pointers to objects that have pointers to SubClass2
SubClass2* object2; //has pointers/smart-pointers to objects that have pointers/smart-pointers to SubClass1
MyClass(){ object2 = new SubClass2(&object1); }; //SubClass2 constructor establishes the relationship between object1 and object2 pointed-to-objects.
};
/*....*/
MyClass* ptr = new MyClass(std::move(MyClass()));
I found out that some pointers inside object2 were invalid. I would love to share the definitions of SubClass1 and SubClass2, but unfortunately I am not allowed to share this code.
My question is, are there certain checklists that a class object must satisfy in order to prevent problems when moving a temporary object to a new heap allocated object? Are there any ways to know before-hand if a move operation from will fail?
Original problem was due to mistakenly writing:
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>(MyClass());
when changing all my shared_ptr instances to be constructed through make_shared instead of new. I ended up narrowing the problem down to raw pointers, std::move, and a temporary rvalue reference.
When doing:
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
it worked perfectly fine.
Curiously, the following had no problems:
MyClass stack_obj{};
MyClass* ptr = new MyClass(std::move(stack_obj));
It seems to be something specific to moving a temporary object to the heap?
I also found out that if I changed object1 to instead be a pointer of SubClass1, I did not have any problems either. i.e.
class MyClass{
public:
SubClass1* object1;
SubClass2* object2;
MyClass(){ object1 = new SubClass1(); object2 = new SubClass2(object1); };
};
/*....*/
MyClass* ptr = new MyClass(std::move(MyClass())); //worked fine
The only problem that I could imagine was due to object1 being created on the stack for the temporary object MyClass(), and then the problem arised from moving object1 from the local stack to the heap during the pointer allocation.
For completion, these are other ways in which I was able to replicate the same problem for the original class:
template <class... Args>
std::shared_ptr<MyClass> make_shared_test(Args&& ... args)
{
return std::shared_ptr<MyClass>(new MyClass(std::forward<Args>(args)...));
}
MyClass&& forward_test(MyClass&& param){
return static_cast<MyClass&&>(param);
}
/*....*/
MyClass* obj1(new MyClass(std::move(MyClass())));
std::shared_ptr<MyClass> obj2 = make_shared_test(MyClass());
std::shared_ptr<MyClass> obj3 = std::shared_ptr<MyClass>(new MyClass(forward_test(MyClass())));
std::shared_ptr<MyClass> obj4 = std::shared_ptr<MyClass>(new MyClass(std::move(MyClass())));
std::shared_ptr<MyClass> obj5(new MyClass(std::move(MyClass())));
I had forgotten the move constructor on SubClass1 (rule of 5). Thus, the compiler would actually copy the class instead of moving it, and once the destructor was called for SubClass1, it would sever the relationship between object1 and object2, causing undefined behavior.