Новое ключевое слово =по умолчанию в C++11



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



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


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



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


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

515   3  

3 ответов:

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

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

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

§8.5.1 / 1 [dcl.в этом.примечанияа] агрегат-это массив или класс без пользовательских конструкторов, [и...]

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

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

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

#include <type_traits>

struct X {
    X() = default;
};

struct Y {
    Y() { };
};

int main() {
    static_assert(std::is_trivial<X>::value, "X should be trivial");
    static_assert(std::is_pod<X>::value, "X should be POD");

    static_assert(!std::is_trivial<Y>::value, "Y should not be trivial");
    static_assert(!std::is_pod<Y>::value, "Y should not be POD");
}

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

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

n2210 приводит несколько причин:

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

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

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

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

посмотреть правило трех становится правилом пяти с C++11?:

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

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

для конструктора по умолчанию можно было бы сделать любой конструктор по умолчанию с пустым телом считаться кандидатом на то, чтобы быть тривиальным конструктором, таким же, как использование =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
};

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

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

struct T { 
  std::shared_ptr<int> 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 для конструкторов по умолчанию один образец. Это просто легче мысленно использовать =default чем отмечать функции со всеми другими специальными ключевыми словами и такими, которые подразумеваются =default и это была одна из тем C++11: сделать язык проще. У него все еще есть много бородавок и компромиссов с обратной связью, но ясно, что это большой шаг вперед от C++03, когда дело доходит до простоты использования.

Comments

    Ничего не найдено.