Когда я изучал 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)
{}
Поведение по умолчанию:
T()
): вызывает базы def. ctors и члены по умолчанию ctors.T(const T&)
): вызывает базу копий. ctors и члены копируют ctors.T(T&&)
): звонки базы перемещаются. ctors и члены перемещают ctors.T& operator=(const T&)
): назначает базы вызовов. и участники назначают.T& operator=(T&&)
): переадресация звонков в базы, переадресация участников.~T()
): вызывает деструктор-член и базовый деструктор (в обратном порядке).Для встроенных типов (int и т. Д.)
Поскольку указатели также являются встроенными типами, это относится к int*
(не на что это указывает).
Теперь, если вы ничего не объявите, ваш T
класс просто будет содержать int *, которому не принадлежит указанный int, поэтому копия T будет просто содержать указатель на тот же int. Это то же самое поведение, что и в C ++ 03. По умолчанию реализовано перемещение для встроенных типов — копирование. Ибо классы перемещаются по элементам (и зависят от того, какие члены: просто копии для встроенных модулей)
Если вам нужно изменить это поведение, вы должны сделать это согласованно: например, если вы хотите «владеть» тем, на что вы указываете, вы необходимость
nullptr
: это определяет «пустое состояние», к которому мы можем обратиться позже.
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 имеет много членов, поскольку при перемещении и назначении требуется своп. Но это может быть обычная функция закрытого члена.