The default behavior is:
- Default ctor (
T()
): calls bases def. ctors and members default ctors.
- Copy ctor (
T(const T&)
): calls bases copy. ctors and members copy ctors.
- Move ctor (
T(T&&)
): calls bases move. ctors and members move ctors.
- Assign (
T& operator=(const T&)
): calls bases assign. and members assign.
- Transfer (
T& operator=(T&&)
): calls bases transfer, and members transfer.
- Destructor (
~T()
): calls member destructor, and bases destructor (reverse order).
For built-in types (int etc.)
- Default ctor: set to 0 if explicitly called
- Copy ctor: bitwise copy
- Move ctor: bitwise copy (no change on the source)
- Assign: bitwise copy
- Transfer: bitwise copy
- Destructor: does nothing.
Since pointers are builtin types as well, this apply to int*
( not to what it points to).
Now, if you don't declare anything, your T
class will just hold a int* that does not own the pointed int, so a copy of T will just hold a pointer to the same int. This is the same resulting behavior as C++03. Default implemented move for built-in types are copy. For classes are memberwise move (and depends on what members are: just copies for built-ins)
If you have to change this behavior, you have to do it coherently: for example, if you want to "own" what you point to, you need
- a default ctor initializing to
nullptr
: this defines an "empty state" we can refer later
- a creator ctor initializing to a given pointer
- a copy ctor initializing to a copy of the pointed (this is the real change)
- a dtor that deletes the pointed
- an assign that deletes the pointed and receive a new copy of the pointed
.
T::T() :_param() {}
T::T(int* s) :_param(s) {}
T(const T& s) :_param(s._param? new int(*s._param): nullptr) {}
~T() { delete _param; } // will do nothing if _param is nullptr
Let's not define the assign, by now, but concentrate on the move:
If you don't declare it, since you declared the copy, it will be deleted: this makes a T object always being copied even if temporary (same behavior as c++03)
But if the source object is temporary, we can create an empty destination and swap them:
T::T(T&& s) :T() { std::swap(_param, s._param); }
This is what is called a move.
Now the assignment: before C++11 T& operator=(const T& s)
should check against a self assignment, make the destination empty and receive a copy of the pointed:
T& operator=(const T& s)
{
if(this == &s) return *this; // we can shortcut
int* p = new int(s._param); //get the copy ...
delete _param; //.. and if succeeded (no exception while copying) ...
_param = p; // ... delete the old and keep the copy
return *this;
}
With C++11 we can use the parameter passing to generate the copy, thus giving
T& operator=(T s) //note the signature
{ std::swap(_param, s._param); return *this; }
Note that this works also in C++98, but the pass-by copy will not be optimized in pass-by move if s
is temporary. This makes such implementation not profitable in C++98 and C++03 but really convenient in C++11.
Note also that there is no need to specialize std::swap
for T: std::swap(a,b);
will work, being implemented as three moves (not copy)
The practice to implement a swap function derives for the case where T has many members, being swap required in both move and assign. But it can be a regular private member function.