c++c++11canonical-form

How does c++11 implements "... = default;" for the rule of three methods


When I learned C++ people told me to always implement at least rule of three methods.

Now I'm seeing the new "... = default;" from c++0x on stack overflow, and my question is:

Is there a c++11 standard implementation defined for those methods or is it compiler specific?

plus I would like to have some precisions:

  • What does the implementation looks like in term of code? (if it's generic)
  • Does this have an advantage compared to my example implementation below?
  • If you don't use assignment/copy constructor, what does *... = delete* do precisly, what's the difference with declaring them private? Answer (from @40two)
  • Is the new default= different from the old default implementation?

Disclaimer: when I'll need more advanced features in my methods, for sure I'll implements them myself. But I get used to implement assignment operator and copy constructor even when I never used them, just in order that the compiler don't.


What I used to do: (edited, @DDrmmr swap/move)

//File T.h
class T
{
  public:
    T(void);
    T(const T &other);
    T(const T &&other);
    T &operator=(T other);
    friend void swap(T &first, T &second);
    ~T(void);

  protected:
    int *_param;
};

//File T.cpp
T::T(void) :
  _param(std::null)
{}

T::T(T &other)
  : _param(other._param)
{}

T::T(T &&other)
  : T()
{
  swap(*this, other);
}

T &T::operator=(T other)
{
  swap(*this, other);
  return (*this);
}

friend void swap(T &first, T &second)
{
  using std::swap;

  swap(first._param, second._param);
}

T::~T(void)
{}

Solution

  • The default behavior is:

    For built-in types (int etc.)

    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

    .

    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.