Конструктор по умолчанию по умолчанию определен как то же самое, что и определяемый пользователем конструктор по умолчанию без списка инициализации и пустой составной оператор.
§12.1 / 6 [class.ctor] Конструктор по умолчанию, который используется по умолчанию и не определен как удаленный, неявно определяется, когда он используется odr для создания объекта своего типа класса или когда он явно используется по умолчанию после его первого объявления. Неявно определенный конструктор по умолчанию выполняет набор инициализаций класса, которые были бы выполнены написанным пользователем конструктором по умолчанию для этого класса без инициализатора ctor (12.6.2) и пустого составного оператора. [...]
Однако, хотя оба конструктора будут вести себя одинаково, предоставление пустой реализации действительно влияет на некоторые свойства класса. Предоставление пользовательского конструктора, даже если он ничего не делает, делает тип не агрегатным, а также нетривиальным . Если вы хотите, чтобы ваш класс был агрегатным или тривиальным типом (или транзитивностью, типом POD), то вам нужно использовать = default.
§8.5.1 / 1 [dcl.init.aggr] Агрегат - это массив или класс без пользовательских конструкторов, [и ...]
§12.1 / 5 [class.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) для каждой из этих специальных функций-членов упрощает чтение кода за счет явного указания вашего намерения.
Почти. 12.1 / 6: «Если этот пользовательский конструктор по умолчанию будет удовлетворять требованиям constexprконструктора (7.1.5), неявно определенный конструктор по умолчанию - это constexpr».
Кейси
На самом деле, 8.4.2 / 2 более информативен: «Если функция явно установлена по умолчанию в своем первом объявлении, (а) она неявно считается constexprтаковой, если неявное объявление будет, (б) она неявно считается имеющей то же самое спецификация исключения, как если бы она была неявно объявлена (15.4), ... "В этом конкретном случае это не имеет значения, но в целом foo() = default;имеет небольшое преимущество перед foo() {}.
Кейси
2
Вы говорите, что нет никакой разницы, а затем продолжаете объяснять различия?
@hvd В этом случае нет никакой разницы, потому что неявного объявления не было бы constexpr(поскольку элемент данных остается неинициализированным), а его спецификация исключения допускает все исключения. Я сделаю это яснее.
Джозеф Мэнсфилд
2
Спасибо за разъяснения. Тем не менее, похоже, что разница есть constexpr(о которой вы упомянули, здесь не должно иметь значения): выдает struct S1 { int m; S1() {} S1(int m) : m(m) {} }; struct S2 { int m; S2() = default; S2(int m) : m(m) {} }; constexpr S1 s1 {}; constexpr S2 s2 {};только s1ошибку, а не s2. И в clang, и в g ++.
10
У меня есть пример, который покажет разницу:
#include<iostream>usingnamespace std;class A
{public:int x;
A(){}};class B
{public:int x;
B()=default;};int main(){int x =5;new(&x)A();// Call for empty constructor, which does nothing
cout << x << endl;new(&x)B;// Call for default constructor
cout << x << endl;new(&x)B();// Call for default constructor + Value initialization
cout << x << endl;return0;}
Вывод:
550
Как мы видим, вызов пустого конструктора A () не инициализирует члены, в то время как B () делает это.
не могли бы вы объяснить этот синтаксис -> new (& x) A ();
Vencat
5
Мы создаем новый объект в памяти, начиная с адреса переменной x (вместо нового выделения памяти). Этот синтаксис используется для создания объекта в заранее выделенной памяти. Как и в нашем случае размер B = размер int, поэтому new (& x) A () создаст новый объект вместо переменной x.
Определения конструктора связаны; объявление любого конструктора подавляет конструктор по умолчанию.
Деструктор по умолчанию не подходит для полиморфных классов, требующих явного определения.
После подавления дефолта нет никаких средств для его восстановления.
Реализации по умолчанию часто более эффективны, чем реализации, указанные вручную.
Реализации, отличные от значений по умолчанию, нетривиальны, что влияет на семантику типов, например, делает тип не-POD.
Не существует способа запретить специальную функцию-член или глобальный оператор без объявления (нетривиальной) замены.
type::type()=default;
type::type(){ x =3;}
В некоторых случаях тело класса может меняться, не требуя изменения определения функции-члена, потому что значение по умолчанию изменяется с объявлением дополнительных членов.
Обратите внимание, что конструктор перемещения и оператор присваивания перемещения не будут созданы для класса, который явно объявляет любую из других специальных функций-членов, этот конструктор копирования и оператор присваивания копии не будут созданы для класса, который явно объявляет конструктор перемещения или перемещение оператор присваивания и что класс с явно объявленным деструктором и неявно определенным конструктором копирования или неявно определенным оператором присваивания копии считается устаревшим
Это причины наличия = defaultв целом, а не причины делать = defaultна конструкторе вместо того, чтобы делать { }.
Джозеф Мэнсфилд
@JosephMansfield Правда, но так как {}было уже особенность языка до начала введения =default, эти причины этого неявно опираются на различия (например , «нет средств , чтобы воскресить [подавленным по умолчанию] нет» означает , что {}это не эквивалентно по умолчанию ).
Кайл Стрэнд
7
В некоторых случаях это вопрос семантики. Это не очень очевидно с конструкторами по умолчанию, но становится очевидным с другими сгенерированными компилятором функциями-членами.
Для конструктора по умолчанию можно было бы сделать любой конструктор по умолчанию с пустым телом кандидатом в тривиальный конструктор, как и при использовании =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, в основном ). Компилятор должен будет проверить каждое выражение инициализатора члена и убедиться, что оно идентично выражению, чтобы получить доступ к соответствующему члену источника и ни к чему другому, убедиться, что ни один член не остался с нетривиальной конструкцией по умолчанию и т. Д. Это происходит в обратном направлении в процессе компилятор будет использовать для проверки того, что собственные сгенерированные версии этой функции тривиальны.
Затем рассмотрим оператор присваивания копии, который может стать еще более привлекательным, особенно в нетривиальном случае. Это тонна изобилия, который вам не нужно писать для многих классов, но вы все равно будете вынуждены писать на 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 operationreturn*this;};
Это простой случай, но это уже больше кода, чем вы когда-либо хотели бы написать для такого простого типа, как T(особенно когда мы бросаем операции перемещения в смесь). Мы не можем полагаться на пустое тело, означающее «заполнить значения по умолчанию», потому что пустое тело уже совершенно правильно и имеет ясное значение. Фактически, если бы пустое тело использовалось для обозначения «заполнения значений по умолчанию», то не было бы никакого способа явно создать конструктор копирования без операции или тому подобное.
Это опять вопрос последовательности. Пустое тело означает «ничего не делать», но для таких вещей, как конструкторы копирования, вы действительно не хотите «ничего не делать», а «делайте все, что вы обычно делаете, если не подавлены». Отсюда =default. Это необходимо для преодоления подавленных функций-членов, генерируемых компилятором, таких как конструкторы копирования / перемещения и операторы присваивания. Тогда просто «очевидно» заставить его работать и для конструктора по умолчанию.
Было бы неплохо сделать конструктор по умолчанию с пустыми телами, а тривиальные члены / базовые конструкторы также можно считать тривиальными, как если бы они =defaultделали старый код более оптимальным в некоторых случаях, но большинство низкоуровневого кода полагалось на тривиальные конструкторы по умолчанию для оптимизации также опираются на тривиальные конструкторы копирования. Если вам нужно пойти и «исправить» все ваши старые конструкторы копирования, вам также не составит труда исправить все ваши старые конструкторы по умолчанию. Это также намного яснее и очевиднее, если использовать явное =defaultдля обозначения ваших намерений.
Есть еще несколько вещей, которые будут выполнять функции-члены, созданные компилятором, и вам также придется явно вносить изменения для поддержки. Поддержка constexprконструкторов по умолчанию является одним из примеров. Это просто проще мысленно использовать, =defaultчем размечать функции всеми другими специальными ключевыми словами и такими, которые подразумеваются, =defaultи это была одна из тем C ++ 11: сделать язык проще. В нем все еще есть много недостатков и компромиссов, но ясно, что это большой шаг вперед по сравнению с C ++ 03, когда дело касается простоты использования.
У меня была проблема, где я ожидал = defaultбы сделать a=0;и не было! Я должен был отбросить это в пользу : a(0). Я все еще не понимаю, насколько это полезно = default, дело в производительности? это сломается где-нибудь, если я просто не использую = default? Я пробовал прочитать здесь все ответы, купить, я новичок в некоторых вещах о С ++, и у меня много проблем с их пониманием.
Водолей Сила
@AquariusPower: дело не только в производительности, но и в некоторых случаях в связи с исключениями и другой семантикой. А именно, оператор по умолчанию может быть тривиальным, но оператор, не заданный по умолчанию, никогда не может быть тривиальным, и некоторый код будет использовать методы метапрограммирования для изменения поведения или даже запрета типов с нетривиальными операциями. Ваш a=0пример из-за поведения тривиальных типов, которые являются отдельной (хотя и связанной) темой.
Шон Мидлдич
Значит ли это, что можно иметь = defaultи все же грант aбудет =0? каким-то образом? как вы думаете, я мог бы создать новый вопрос, например, «как создать конструктор = defaultи предоставить поля, которые будут правильно инициализированы?», кстати, у меня была проблема в a, structа не в a class, и приложение работает правильно, даже не используя = default, я могу добавьте минимальную структуру на этот вопрос, если он хороший :)
Водолей Сила
1
@AquariusPower: вы можете использовать нестатические инициализаторы элементов данных. Напишите свою структуру так: struct { int a = 0; };если вы затем решите, что вам нужен конструктор, вы можете использовать его по умолчанию, но обратите внимание, что тип не будет тривиальным (что нормально).
Шон Миддледич
2
Из-за устаревания std::is_podи его альтернативы std::is_trivial && std::is_standard_layoutфрагмент из ответа @JosephMansfield становится следующим:
#include<type_traits>struct X {
X()=default;};struct Y {
Y(){}};int main(){static_assert(std::is_trivial_v<X>,"X should be trivial");static_assert(std::is_standard_layout_v<X>,"X should be standard layout");static_assert(!std::is_trivial_v<Y>,"Y should not be trivial");static_assert(std::is_standard_layout_v<Y>,"Y should be standard layout");}
Обратите внимание, что по- Yпрежнему стандартного формата.
default
не новое ключевое слово, это просто новое использование уже зарезервированного ключевого слова.Ответы:
Конструктор по умолчанию по умолчанию определен как то же самое, что и определяемый пользователем конструктор по умолчанию без списка инициализации и пустой составной оператор.
Однако, хотя оба конструктора будут вести себя одинаково, предоставление пустой реализации действительно влияет на некоторые свойства класса. Предоставление пользовательского конструктора, даже если он ничего не делает, делает тип не агрегатным, а также нетривиальным . Если вы хотите, чтобы ваш класс был агрегатным или тривиальным типом (или транзитивностью, типом POD), то вам нужно использовать
= default
.Демонстрировать:
Кроме того, явная установка конструктора по умолчанию сделает это,
constexpr
если бы неявный конструктор был бы, а также предоставит ему ту же спецификацию исключения, которую имел бы неявный конструктор. В случае, который вы указали, неявного конструктора не было быconstexpr
(потому что он оставил бы элемент данных неинициализированным), и он также будет иметь пустую спецификацию исключения, поэтому нет никакой разницы. Но да, в общем случае вы можете вручную указатьconstexpr
и спецификацию исключения, чтобы она соответствовала неявному конструктору.Использование
= default
действительно приносит некоторую единообразие, потому что его также можно использовать с конструкторами и деструкторами копирования / перемещения. Например, пустой конструктор копирования не будет делать то же самое, что конструктор копирования по умолчанию (который будет выполнять поэлементное копирование своих членов). Единообразное использование синтаксиса= default
(или= delete
) для каждой из этих специальных функций-членов упрощает чтение кода за счет явного указания вашего намерения.источник
constexpr
конструктора (7.1.5), неявно определенный конструктор по умолчанию - этоconstexpr
».constexpr
таковой, если неявное объявление будет, (б) она неявно считается имеющей то же самое спецификация исключения, как если бы она была неявно объявлена (15.4), ... "В этом конкретном случае это не имеет значения, но в целомfoo() = default;
имеет небольшое преимущество передfoo() {}
.constexpr
(поскольку элемент данных остается неинициализированным), а его спецификация исключения допускает все исключения. Я сделаю это яснее.constexpr
(о которой вы упомянули, здесь не должно иметь значения): выдаетstruct S1 { int m; S1() {} S1(int m) : m(m) {} }; struct S2 { int m; S2() = default; S2(int m) : m(m) {} }; constexpr S1 s1 {}; constexpr S2 s2 {};
толькоs1
ошибку, а неs2
. И в clang, и в g ++.У меня есть пример, который покажет разницу:
Вывод:
Как мы видим, вызов пустого конструктора A () не инициализирует члены, в то время как B () делает это.
источник
N2210 приводит несколько причин:
Видите, Правило трех становится Правилом пяти с C ++ 11? :
источник
= default
в целом, а не причины делать= default
на конструкторе вместо того, чтобы делать{ }
.{}
было уже особенность языка до начала введения=default
, эти причины этого неявно опираются на различия (например , «нет средств , чтобы воскресить [подавленным по умолчанию] нет» означает , что{}
это не эквивалентно по умолчанию ).В некоторых случаях это вопрос семантики. Это не очень очевидно с конструкторами по умолчанию, но становится очевидным с другими сгенерированными компилятором функциями-членами.
Для конструктора по умолчанию можно было бы сделать любой конструктор по умолчанию с пустым телом кандидатом в тривиальный конструктор, как и при использовании
=default
. В конце концов, старые пустые конструкторы по умолчанию были допустимы для C ++ .То, понимает ли компилятор этот конструктор как тривиальный, в большинстве случаев не имеет значения, кроме оптимизаций (ручных или компиляционных).
Тем не менее, эта попытка обработать пустые тела функций как «по умолчанию» не работает для других типов функций-членов. Рассмотрим конструктор копирования:
В приведенном выше случае конструктор копирования, написанный с пустым телом, теперь неверен . Он больше ничего не копирует. Этот набор семантик сильно отличается от семантики конструктора копирования по умолчанию. Желаемое поведение требует, чтобы вы написали код:
Однако даже в этом простом случае для компилятора становится гораздо сложнее проверять, что конструктор копирования идентичен тому, который он генерирует сам, или видеть, что конструктор копирования тривиален (эквивалентен
memcpy
, в основном ). Компилятор должен будет проверить каждое выражение инициализатора члена и убедиться, что оно идентично выражению, чтобы получить доступ к соответствующему члену источника и ни к чему другому, убедиться, что ни один член не остался с нетривиальной конструкцией по умолчанию и т. Д. Это происходит в обратном направлении в процессе компилятор будет использовать для проверки того, что собственные сгенерированные версии этой функции тривиальны.Затем рассмотрим оператор присваивания копии, который может стать еще более привлекательным, особенно в нетривиальном случае. Это тонна изобилия, который вам не нужно писать для многих классов, но вы все равно будете вынуждены писать на C ++ 03:
Это простой случай, но это уже больше кода, чем вы когда-либо хотели бы написать для такого простого типа, как
T
(особенно когда мы бросаем операции перемещения в смесь). Мы не можем полагаться на пустое тело, означающее «заполнить значения по умолчанию», потому что пустое тело уже совершенно правильно и имеет ясное значение. Фактически, если бы пустое тело использовалось для обозначения «заполнения значений по умолчанию», то не было бы никакого способа явно создать конструктор копирования без операции или тому подобное.Это опять вопрос последовательности. Пустое тело означает «ничего не делать», но для таких вещей, как конструкторы копирования, вы действительно не хотите «ничего не делать», а «делайте все, что вы обычно делаете, если не подавлены». Отсюда
=default
. Это необходимо для преодоления подавленных функций-членов, генерируемых компилятором, таких как конструкторы копирования / перемещения и операторы присваивания. Тогда просто «очевидно» заставить его работать и для конструктора по умолчанию.Было бы неплохо сделать конструктор по умолчанию с пустыми телами, а тривиальные члены / базовые конструкторы также можно считать тривиальными, как если бы они
=default
делали старый код более оптимальным в некоторых случаях, но большинство низкоуровневого кода полагалось на тривиальные конструкторы по умолчанию для оптимизации также опираются на тривиальные конструкторы копирования. Если вам нужно пойти и «исправить» все ваши старые конструкторы копирования, вам также не составит труда исправить все ваши старые конструкторы по умолчанию. Это также намного яснее и очевиднее, если использовать явное=default
для обозначения ваших намерений.Есть еще несколько вещей, которые будут выполнять функции-члены, созданные компилятором, и вам также придется явно вносить изменения для поддержки. Поддержка
constexpr
конструкторов по умолчанию является одним из примеров. Это просто проще мысленно использовать,=default
чем размечать функции всеми другими специальными ключевыми словами и такими, которые подразумеваются,=default
и это была одна из тем C ++ 11: сделать язык проще. В нем все еще есть много недостатков и компромиссов, но ясно, что это большой шаг вперед по сравнению с C ++ 03, когда дело касается простоты использования.источник
= default
бы сделатьa=0;
и не было! Я должен был отбросить это в пользу: a(0)
. Я все еще не понимаю, насколько это полезно= default
, дело в производительности? это сломается где-нибудь, если я просто не использую= default
? Я пробовал прочитать здесь все ответы, купить, я новичок в некоторых вещах о С ++, и у меня много проблем с их пониманием.a=0
пример из-за поведения тривиальных типов, которые являются отдельной (хотя и связанной) темой.= default
и все же грантa
будет=0
? каким-то образом? как вы думаете, я мог бы создать новый вопрос, например, «как создать конструктор= default
и предоставить поля, которые будут правильно инициализированы?», кстати, у меня была проблема в a,struct
а не в aclass
, и приложение работает правильно, даже не используя= default
, я могу добавьте минимальную структуру на этот вопрос, если он хороший :)struct { int a = 0; };
если вы затем решите, что вам нужен конструктор, вы можете использовать его по умолчанию, но обратите внимание, что тип не будет тривиальным (что нормально).Из-за устаревания
std::is_pod
и его альтернативыstd::is_trivial && std::is_standard_layout
фрагмент из ответа @JosephMansfield становится следующим:Обратите внимание, что по-
Y
прежнему стандартного формата.источник