Files CCore/inc/Move.h CCore/src/Move.cpp
Files CCore/inc/ToMoveCtor.h CCore/src/ToMoveCtor.cpp
First of all, CCore Move is not a C++ move. Don't mess it up. Move is designed to solve the following task: assume you have an object and you want to move it to another memory location. Once you have an appropriate block of memory, you need to create the copy of the object there and destroy the original object as the one operation. Unlike the copy operation, Move can be implemented as an efficient operation for a wide range of types. This is because we don't need to create an additional state, we just have to replace the existing one into another memory location. So before the operation we have the object and the raw memory, after we have the raw memory and the moved object.
There is a technical difficulty with the Move implementation: C++ requires a some constructor call for the object creation and the destructor call for the object life-time termination (with some minor exceptions). To make the moving derivable we have to use the Move constructor as the implementation point for it.
template <class T> T * CopyMove(T *obj,Place<void> place) noexcept; // for efficiently copyable classes template <class T> T * SwapMove(T *obj,Place<void> place) noexcept; // for classes with objSwap() and efficient default constructor template <class T> T * MoveMove(T *obj,Place<void> place) noexcept; // for classes with std move template <class T> T * CtorMove(T *obj,Place<void> place) noexcept; // for classes with Move constructor template <class T> T * Move(T *obj,Place<void> place) noexcept; // general
Normally you should use the function Move() for Move operation. If the destined type is a class type with the method objMove(), it is called to do the job. Otherwise, if the destined type has a Move constructor, CtorMove() is used. Otherwise, if the destined type is std movable, MoveMove() is used. Otherwise, if the destined type is a class type with the method objSwap(), the SwapMove() is used. In this case class must have the efficient default constructor. Finally, CopyMove() is called for remaining cases. It is assumed, that such types are efficiently copyable.
The most convenient way to make a class movable is to define the method objSwap() and make it both swappable and movable. But for the sake of efficiency, you may define the method objMove() or the Move constructor. Consider the following example:
struct S { A a; // class A has Move constructor B b; // type B is efficiently copyable C c; // class C has Move constructor // swap/move objects void objSwap(S &obj) { Swap(a,obj.a); Swap(b,obj.b); Swap(c,obj.c); } explicit S(ToMoveCtor<S> obj) // the Move constructor signature : a(ObjToMove(obj->a)), // member move with its Move constructor b(obj->b), // member move by copying c(ObjToMove(obj->c)) // member move with its Move constructor { } };
The reason to define the Move constructor is to support the further derivation of it. The Move constructor is called in the following context: an object is being moving, the original object will be destroyed by the call of destructor right after Move constructor is finished. Here is the template for the moving of Resource Host Types.
class H : NoCopy { private: ResourceHandle h; // Small Data Type public: H() noexcept { SetNull(h); } H(A a,B b,C c) { h=New(a,b,c); } ~H() { if( NotNull(h) ) Delete(h); } // may help compiler to eliminate the following // // SetNull(h); // if( NotNull(h) ) Delete(h); // end-of-object-life-time; // // sequence of actions // move objects explicit H(ToMoveCtor<H> obj) { h=obj->h; SetNull(obj->h); } };
The class ToMoveCtor<T> is a simple wrapper over the pointer to the type T:
/* struct ToMoveCtor<T> */
template <class T>
struct ToMoveCtor
{
T *obj;
explicit ToMoveCtor(T &obj_) { obj=&obj_; }
T * operator -> () const { return obj; }
template <class S>
ToMoveCtor<S> cast() const { return ToMoveCtor<S>(*obj); }
};
It is solely introduced to designate Move constructors. The method cast() can be used to "touch" a base class subobject in the Move constructor's body.
The method objMove() must follow this pattern:
class SomeClass { public: SomeClass * objMove(Place<void> place); };
It is rarely needed. It must replace the object into another memory location, given by the argument, and return the pointer to the new object. The original object is destroyed after this operation.
The Move constructor of some class looks like:
class SomeClass { public: SomeClass(ToMoveCtor<SomeClass> obj); };
It is a special class member and it is used by the CtorMove() like this:
template <class T>
T * CtorMove(T *obj,Place<void> place) noexcept
{
T *ret=new(place) T(ObjToMove(*obj));
obj->~T();
return ret;
}
template <class T>
ToMoveCtor<T> ObjToMove(T &obj) { return ToMoveCtor<T>(obj); }
This constructor must move the object, and it may assume, that the original object is destroyed right after the move operation. This is different than the C++ move constructor (more relaxed).
Moving with C++ move constructor is performed the same way:
template <class T> T * MoveMove(T *obj,Place<void> place) noexcept { T *ret=new(place) T(std::move(*obj)); obj->~T(); return ret; }
This is the SwapMove():
template <class T> T * SwapMove(T *obj,Place<void> place) noexcept { T *ret=new(place) T(); Swap(*obj,*ret); obj->~T(); return ret; }
It is performed by the creating the default object at the new memory location, then swapping the states with the original object and, finally, by the destroying the original object. This is the model way to do the moving. Other ways must achieve the same result, by more efficiently.