Как реализует С++ 11 = default; по правилу трех методов

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

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

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

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

  • Как выглядит реализация с точки зрения кода? (если он общий)
  • Есть ли у этого преимущество по сравнению с реализацией моего примера ниже?
  • Если вы не используете конструктор присваивания/копирования, что именно делает *... = delete*, в чем разница с объявлением их частными? Ответ (от @40two)
  • Отличается ли новый 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)
{}

person Community    schedule 27.06.2014    source источник
comment
Ваш оператор присваивания не является безопасным для исключений, если у вас есть более одной переменной-члена, которую может выдать оператор присваивания.   -  person D Drmmr    schedule 27.06.2014
comment
Для справки, методы формы Коплина кажутся широко известным правилом трех (что должно быть правилом пяти в C++ 11 и более поздних версиях) плюс конструктор.   -  person Some programmer dude    schedule 27.06.2014
comment
Hum никогда раньше не сталкивался с этой проблемой, потому что обычно они безопасны в плане исключений, но что бы вы сделали с этим? Просто знать   -  person    schedule 27.06.2014
comment
@JoachimPileborg Да, похоже, это правило трех, но я не выучил его по-английски. Отредактировано.   -  person    schedule 27.06.2014
comment
Не нужно редактировать, большинство опытных программистов узнают его, когда увидят.   -  person Some programmer dude    schedule 27.06.2014
comment
@Mayerz Вы можете использовать идиому копирования и подкачки для реализации задания оператор, использующий конструктор копирования, обеспечивая при этом надежную гарантию безопасности исключений.   -  person D Drmmr    schedule 27.06.2014
comment
@DDrmMR очень интересно, спасибо за совет.   -  person    schedule 27.06.2014
comment
Когда я изучал C++, люди говорили мне всегда реализовывать как минимум правило трех методов. — если я вас правильно понял, это противоречит правилу трех, как обычно представляется. Суть правила трех заключается в том, что если вам нужен любой из трех методов (деструктор, копирующий ctor, оператор присваивания), вам почти наверняка понадобятся и два других. Это не означает, что вы всегда должны реализовывать эти три. Многие хорошо написанные классы, например те, членами которых являются только контейнеры STL и интеллектуальные указатели, не нуждаются ни в одном из методов правила трех.   -  person user4815162342    schedule 27.06.2014
comment
If you don't use assignment/copy constructor, what does *... = delete* do precisly, what's the difference with declaring them private? stackoverflow.com/questions/ 18847957/   -  person 101010    schedule 27.06.2014
comment
Да, я знаю, но я так научился, теперь я понимаю, что иногда это не нужно. они сказали нам, что даже если они вам не нужны, вы должны объявить об этом, чтобы компилятор не сделал этого, это правильно или?   -  person    schedule 27.06.2014
comment
Если вы объявите их, компилятор этого не сделает. Но принцип состоит в том, чтобы позволить компилятору определять их как можно чаще. Создавайте свои классы таким образом, чтобы созданные компилятором значения по умолчанию были для вас правильным решением. Только если вам нужна специальная обработка в одной из функций, реализуйте ее. Затем правило трех гласит, что в таком случае вам, скорее всего, потребуется специальная обработка всех трех, и поэтому вы должны реализовать их все. Вот хорошая статья на эту тему.   -  person Angew is no longer proud of SO    schedule 27.06.2014
comment
@Angew Спасибо за ссылку и объяснение. Раньше я реализовывал их пустыми, чтобы компилятор не делал этого, когда они мне не нужны. хорошо плохо?   -  person    schedule 27.06.2014
comment
Лучше всего позволить компилятору реализовать методы за вас. Только в тех случаях, когда компилятор не может сделать это должным образом (обычно когда член класса является указателем), вы должны предотвратить это, объявив и/или определив свой собственный.   -  person user4815162342    schedule 27.06.2014
comment
@Mayerz Это зависит. Если вы хотите, чтобы ваш класс не копировался (и не перемещался), во что бы то ни стало предотвратите автоматическое создание функций копирования (в С++ 11 вы можете использовать для этого = delete). Но я бы сказал, что в типичных случаях некопируемые неподвижные классы встречаются не так часто. C++ — это не Java, мы обычно предпочитаем хранить наши объекты по значению.   -  person Angew is no longer proud of SO    schedule 27.06.2014
comment
Явное назначение функций по умолчанию может быть опасным, поскольку оно может сделать доступными функции, которые иначе компилятор не предоставил бы. Не ставьте все по умолчанию вслепую. -- Но если они вам нужны, могут пригодиться явно заданные по умолчанию функции. Например, они могут быть тривиальными (в реализациях, предоставляемых пользователями, этого не может быть). Это влияет на свойства типа класса, например. независимо от того, является ли это POD.   -  person dyp    schedule 27.06.2014


Ответы (1)


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

  • ctor по умолчанию ( T() ): вызывает базы def. ctors и члены по умолчанию ctors.
  • Копировать ctor ( T(const T&) ): вызывает копирование базы. ctors и участники копируют ctors.
  • Move 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-создатель, инициализирующий заданный указатель
  • копировщик, инициализирующий копию указанного (это реальное изменение)
  • dtor, который удаляет указанный
  • назначение, которое удаляет указанное и получает новую копию указанного

.

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 для T: std::swap(a,b); будет работать, будучи реализованным как три хода (не копирование).

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

person Emilio Garavaglia    schedule 27.06.2014
comment
но проходная копия не будет оптимизирована при проходном перемещении, если s является временным Если аргумент является временным, вероятно, произойдет удаление копии. - person dyp; 27.06.2014
comment
@dyp: спасибо, что указали на очень запутанную опечатку. Насчет исключения копирования: да, но в C++03 это просто функция компиляции. В С++ 11 это спец. требование. - person Emilio Garavaglia; 27.06.2014
comment
Да, перемещение из rvalue является требованием для типов, которые его поддерживают. Однако обычное исключение копирования для временных файлов реализовано во всех известных мне компиляторах даже в режиме отладки, и это даже быстрее, чем перемещение. В C++11 это распространяется, конечно, на move elision. Таким образом, в большинстве случаев поведение этого кода (передача rvalue в функцию, которая принимает значение) не будет отличаться в C++03 и C++11. (Если вы не принимаете во внимание конверсии.) - person dyp; 27.06.2014
comment
Спасибо за этот отличный ответ @EmilioGaravaglia. Я просто не понял этого ›Обратите внимание, что нет необходимости специализировать std::swap для T: std::swap(a,b); будет работать, реализуется как три хода (не копирование) - person ; 27.06.2014
comment
@Mayerts: std::swap реализовано (в версии C++11) как c(std::move(a)); a=std::move(b); b=std::move(c);, если класс подвижен, a и b меняют местами свои указатели. Если класс не перемещаемый, то получатся обычные копии, так как T&& распадается на T const&. - person Emilio Garavaglia; 27.06.2014