Как в C++11 реализовано "... = default;" для правила трех методов

Когда я изучал C++, люди говорили мне, чтобы я всегда реализовывал хотя бы правило трех методов.

Теперь я вижу новый "... = default;" из C++0x переполнение стека, и мой вопрос:

Существует ли стандартная реализация C++11, определенная для этих методов, или она зависит от компилятора?

плюс я хотел бы иметь некоторые точности:

  • Как выглядит реализация в терминах кода? (если это общее)
  • Это имеет преимущество по сравнению с моим примером реализации ниже?
  • Если вы не используете конструктор присваивания / копирования, что делает *... = delete* точно, какая разница с объявлением их частными? Ответ (от @40 два)
  • Новый default= отличается от старой реализации по умолчанию?

Отказ от ответственности: когда мне понадобятся более продвинутые функции в моих методах, я обязательно сам их реализую. Но я привыкла реализовывать оператор присваивания и конструктор копирования даже тогда, когда я их никогда не использовал, просто для того, чтобы компилятор этого не делал.


Что я делал: (отредактировано, @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)
{}

1 ответ

Решение

Поведение по умолчанию:

  • Ctor по умолчанию (T()): вызывает базы def. ctors и члены по умолчанию ctors.
  • Копировать ctor (T(const T&)): вызывает базу копий. ctors и члены копируют ctors.
  • Переместить ctor (T(T&&)): звонки базы перемещаются. ctors и члены перемещают ctors.
  • Назначить (T& operator=(const T&)): назначает базы вызовов. и участники назначают.
  • Перечислить (T& operator=(T&&)): переадресация звонков в базы, переадресация участников.
  • Деструктор (~T()): вызывает деструктор члена и базовый деструктор (в обратном порядке).

Для встроенных типов (int и т. Д.)

  • Ctor по умолчанию: установлен в 0, если явно вызывается
  • Копировать ctor: побитовое копирование
  • Переместить ctor: побитовое копирование (без изменений в источнике)
  • Назначить: побитовая копия
  • Передача: побитовая копия
  • Деструктор: ничего не делает.

Поскольку указатели также являются встроенными типами, это относится к int* (не на что это указывает).

Теперь, если вы ничего не объявите, ваш T класс просто будет содержать int*, которому не принадлежит указанный int, поэтому копия T будет просто содержать указатель на тот же int. Это то же самое поведение, что и в C++03. По умолчанию реализовано перемещение для встроенных типов - копирование. Ибо классы перемещаются по элементам (и зависит от того, какие члены: просто копии для встроенных модулей)

Если вам нужно изменить это поведение, вы должны сделать это согласованно: например, если вы хотите "владеть" тем, на что вы указываете, вам нужно

  • инициализация ctor по умолчанию nullptr: это определяет "пустое состояние", к которому мы можем обратиться позже
  • Ctor создателя, инициализирующий данный указатель
  • копия ctor, инициализирующая копию указанного (это реальное изменение)
  • дтор, который удаляет остроконечный
  • присвоение, которое удаляет острие и получает новую копию

,

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

Давайте не будем сейчас определять присвоение, а сконцентрируемся на перемещении: если вы не объявите его, так как вы объявили копию, она будет удалена: это делает объект T всегда копируемым, даже если он временный (такое же поведение, как у ++03)

Но если исходный объект является временным, мы можем создать пустой пункт назначения и поменять их местами:

T::T(T&& s) :T() { std::swap(_param, s._param); }

Это то, что называется ходом.

Теперь о назначении: до C++11 T& operator=(const T& s) следует проверить себя самостоятельно, сделать место назначения пустым и получить копию указанного:

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;
}

В C++11 мы можем использовать передачу параметров для генерации копии, что дает

T& operator=(T s) //note the signature
{ std::swap(_param, s._param); return *this; }

Обратите внимание, что это работает также в C++98, но копия при передаче не будет оптимизирована при передаче при передаче, если s это временно. Это делает такую ​​реализацию не выгодной в C++ 98 и C++ 03, но действительно удобной в C++11.

Обратите внимание, что нет необходимости специализироваться std::swap для Т: std::swap(a,b); будет работать, будучи реализованным как три хода (не копировать)

Практика реализации функции подкачки вытекает для случая, когда в T много членов, поскольку при перемещении и назначении требуется своп. Но это может быть обычная функция закрытого члена.

Другие вопросы по тегам