Новое ключевое слово = default в C ++ 11

Я не понимаю, зачем мне это делать:

struct S { int a; S(int aa) : a(aa) {} S() = default; }; 

Почему бы просто не сказать:

 S() {} // instead of S() = default; 

зачем вводить новое ключевое слово?

По умолчанию конструктор по умолчанию определен как те же, что и пользовательский конструктор по умолчанию без списка инициализации и пустой составной инструкции.

§12.1 / 6 [class.ctor] Конструктор по умолчанию, который по умолчанию и не определен как удаленный, неявно определяется, когда он является odr, используется для создания объекта его типа classа или когда он явно дефолт после его первого объявления. Неявно определенный конструктор по умолчанию выполняет набор инициализаций classа, который будет выполняться написанным пользователем конструктором по умолчанию для этого classа без инициализатора ctor (12.6.2) и пустой составной инструкции. […]

Однако, хотя оба конструктора будут вести себя одинаково, предоставление пустой реализации влияет на некоторые свойства classа. Предоставление пользовательского конструктора, даже если он ничего не делает, делает тип не совокупным, а также не тривиальным . Если вы хотите, чтобы ваш class представлял собой совокупность или тривиальный тип (или транзитивность, тип POD), тогда вам нужно использовать = default .

§8.5.1 / 1 [dcl.init.aggr] Агрегат – это массив или class без конструкторов, предоставляемых пользователем, [и …]

§12.1 / 5 [class.ctor] Конструктор по умолчанию является тривиальным, если он не предоставляется пользователем и […]

§9 / 6 [class] Тривиальный class – это class, который имеет тривиальный конструктор по умолчанию и […]

Демонстрировать:

 #include  struct X { X() = default; }; struct Y { Y() { }; }; int main() { static_assert(std::is_trivial::value, "X should be trivial"); static_assert(std::is_pod::value, "X should be POD"); static_assert(!std::is_trivial::value, "Y should not be trivial"); static_assert(!std::is_pod::value, "Y should not be POD"); } 

Кроме того, явно дефолт конструктора сделает его constexpr если бы неявный конструктор был бы, и также даст ему ту же самую спецификацию исключения, которую имел бы неявный конструктор. В случае, который вы constexpr , неявный конструктор не был бы constexpr (потому что он оставил бы член данных неинициализированным), и он также имел бы пустую спецификацию исключения, поэтому нет никакой разницы. Но да, в общем случае вы можете вручную указать constexpr и спецификацию исключения для соответствия неявному конструктору.

Использование = default действительно привносит некоторую однородность, поскольку его также можно использовать с конструкторами и деструкторами copy / move. Например, пустой конструктор копий не будет делать то же самое, что и конструктор копии по умолчанию (который будет выполнять членскую копию своих членов). Использование синтаксиса = default (или = delete ) равномерно для каждой из этих специальных функций-членов делает ваш код более легким для чтения, явно указывая ваши намерения.

В n2210 приводятся некоторые причины:

Управление дефолтами имеет несколько проблем:

  • Определения конструктора связаны; объявление любого конструктора подавляет конструктор по умолчанию.
  • По умолчанию деструктор не подходит для полиморфных classов, требующих явного определения.
  • Когда значение по умолчанию подавлено, нет средств для его восстановления.
  • Реализации по умолчанию часто более эффективны, чем описанные вручную реализации.
  • Нестандартные реализации являются нетривиальными, что влияет на семантику типов, например, делает тип не-POD.
  • Нельзя запрещать специальную функцию-член или глобальный оператор без объявления (нетривиальной) замены.

 type::type() = default; type::type() { x = 3; } 

В некоторых случаях тело classа может меняться, не требуя изменения в определении функции члена, поскольку изменения по умолчанию изменяются с объявлением дополнительных членов.

См. « Правило-тройка» становится «Правило пяти» с C ++ 11? :

Обратите внимание, что оператор перемещения и оператор назначения перемещения не будут сгенерированы для classа, который явно объявляет любую из других специальных функций-членов, что оператор-конструктор копирования и оператор присваивания копии не будут сгенерированы для classа, который явно объявляет конструктор перемещения или перемещает оператора присваивания и что class с явно объявленным деструктором и неявно определенным конструктором копирования или неявно определенным оператором присваивания копии считается устаревшим

В некоторых случаях это вопрос семантики. Это не очень очевидно с конструкторами по умолчанию, но это становится очевидным с другими функциями-членами-компиляторами.

Для конструктора по умолчанию было бы возможно, чтобы любой конструктор по умолчанию с пустым телом считался кандидатом на тривиальный конструктор, так же как и с использованием =default . В конце концов, старые пустые конструкторы по умолчанию были законными C ++ .

 struct S { int a; S() {} // legal C++ }; 

Независимо от того, компилятор понимает, что этот конструктор является тривиальным, в большинстве случаев не имеет значения за пределами оптимизаций (ручных или компиляторных).

Однако эта попытка обработки пустых тел функций как «по умолчанию» полностью разрушается для других типов функций-членов. Рассмотрим конструктор копирования:

 struct S { int a; S() {} S(const S&) {} // legal, but semantically wrong }; 

В приведенном выше случае конструктор копирования, написанный с пустым телом, теперь ошибочен . Это больше не копирует ничего. Это совершенно другой набор семантики, чем семантика конструктора экземпляра по умолчанию. Желаемое поведение требует, чтобы вы написали код:

 struct S { int a; S() {} S(const S& src) : a(src.a) {} // fixed }; 

Однако даже в этом простом случае компилятору становится гораздо больше, чтобы проверить, что конструктор копирования идентичен тому, который он сам сгенерировал, или для того, чтобы увидеть, что конструктор копирования тривиален (эквивалентен memcpy , в основном). Компилятор должен будет проверить выражение инициализатора каждого члена и убедиться, что он идентичен выражению для доступа к соответствующему элементу источника и никому другому, убедитесь, что у членов нет нетривиальной конструкции по умолчанию и т. Д. Это обратное движение способом компилятор будет использовать, чтобы проверить, что его собственные сгенерированные версии этой функции тривиальны.

Рассмотрим тогда оператор присваивания копии, который может стать еще более волосатым, особенно в нетривиальном случае. Это тонна котельной плиты, которую вы не хотите писать для многих classов, но вы все равно вынуждены в C ++ 03:

 struct T { std::shared_ptr b; T(); // the usual definitions T(const T&); T& operator=(const T& src) { if (this != &src) // not actually needed for this simple example b = src.b; // non-trivial operation return *this; }; 

Это простой случай, но это уже больше кода, чем вы когда-либо хотели бы заставить писать для такого простого типа, как T (особенно когда мы бросаем операции перемещения в микс). Мы не можем полагаться на пустое тело, означающее «заполнить значения по умолчанию», потому что пустое тело уже отлично действует и имеет четкое значение. На самом деле, если пустое тело использовалось для обозначения «заполнения значений по умолчанию», тогда не было бы возможности явно создать конструктор копирования no-op или тому подобное.

Это снова вопрос согласованности. Пустое тело означает «ничего не делать», но для таких вещей, как конструкторы копирования, вы действительно не хотите «ничего не делать», а скорее «делайте все, что обычно делаете, если не подавляете». Следовательно =default . Это необходимо для преодоления подавленных функций-сгенерированных компилятором, таких как конструкторы копирования / перемещения и операторы присваивания. Это просто «очевидно», чтобы заставить его работать и для конструктора по умолчанию.

Возможно, было бы неплохо создать конструктор по умолчанию с пустым телом, а тривиальные конструкторы элементов / оснований также считаются тривиальными, как это было бы с =default если бы только в некоторых случаях сделать более старый код более оптимальным, но большинство низкоуровневых кодов, полагающихся на тривиальных конструкторах по умолчанию для оптимизаций также полагаются тривиальные конструкторы копирования. Если вам придётся пойти и «исправить» все ваши старые конструкторы копий, на самом деле не так много усилий, чтобы исправить все ваши старые конструкторы по умолчанию. Это также намного яснее и понятнее, используя явное =default для обозначения ваших намерений.

Есть еще кое-что, что создаваемые компилятором функции-члены будут делать то, что вам придется явно вносить изменения в поддержку. constexpr из примеров является поддержка constexpr для конструкторов по умолчанию. Просто умнее использовать =default чем иметь возможность разметки функций со всеми другими специальными ключевыми словами и такими, которые подразумеваются по =default и это была одна из тем C ++ 11: сделать язык проще. У него все еще есть много бородавок и компромиссов с обратной связью, но ясно, что это большой шаг вперед от C ++ 03, когда дело доходит до простоты использования.

  • Захват ссылки по ссылке в C ++ 11 lambda
  • «Int size = 10;» дает постоянное выражение?
  • Как завершить stream в C ++ 11?
  • gcc может скомпилировать вариационный шаблон, в то время как clang не может
  • Когда я должен действительно использовать noexcept?
  • Ссылка Rvalue обрабатывается как Lvalue?
  • stoi и std :: to_string on mingw 4.7.1
  • Как я могу реализовать счетчик ABA с C ++ 11 CAS?
  • unique_ptr boost эквивалент?
  • Как создать события таймера с помощью C ++ 11?
  • Disambiguate перегруженный указатель функции участника передается как параметр шаблона
  • Давайте будем гением компьютера.