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