Как в c ++ 11 реализовано & quot; … = default; & quot; для правила трех методов

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

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

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

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

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

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


Что я делал: (отредактировано, @DDrmmr своп / ход)

//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)
{}

7

Решение

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

  • 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 всегда копируемым, даже если он временный (такое же поведение, как в c ++ 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 имеет много членов, поскольку при перемещении и назначении требуется своп. Но это может быть обычная функция закрытого члена.

4

Другие решения