Стандарт C ++ (раздел 8.5) гласит:
Если программа вызывает инициализацию по умолчанию объекта типа T с определением const, T должен быть типом класса с конструктором по умолчанию, предоставляемым пользователем.
Зачем? Я не могу придумать ни одной причины, по которой в этом случае требуется пользовательский конструктор.
struct B{
B():x(42){}
int doSomeStuff() const{return x;}
int x;
};
struct A{
A(){}//other than "because the standard says so", why is this line required?
B b;//not required for this example, just to illustrate
//how this situation isn't totally useless
};
int main(){
const A a;
}
a
, но gcc-4.3.4 принимает ее, даже когда вы это делаете (см. Ideone.com/uHvFS )a
. Комо выдает ошибку «константная переменная« a »требует инициализатора - класс« A »не имеет явно объявленного конструктора по умолчанию», если строка закомментирована.const A a{}
:)Ответы:
Это было сочтено дефектом (по отношению ко всем версиям стандарта) и устранено дефектом 253 основной рабочей группы (CWG) . Новая формулировка стандартных состояний в http://eel.is/c++draft/dcl.init#7
Эта формулировка по сути означает, что очевидный код работает. Если вы инициализируете все свои базы и элементы, вы можете сказать,
A const a;
независимо от того, как или если вы пишете какие-либо конструкторы.gcc принимает это с 4.6.4. clang принял это с 3.9.0. Visual Studio также принимает это (по крайней мере, в 2017 году, не уверен, что раньше).
источник
struct A { int n; A() = default; }; const A a;
хотя разрешает,struct B { int n; B() {} }; const B b;
потому что в новой формулировке по-прежнему говорится «предоставлено пользователем», а не «объявлено пользователем», и я остаюсь ломать голову, почему комитет решил исключить конструкторы по умолчанию с явным значением по умолчанию из этого DR, вынуждая нас сделать наши классы нетривиальны, если нам нужны константные объекты с неинициализированными членами.MyPOD
будучи PODstruct
,static MyPOD x;
- опираясь на нулевой инициализации (это то , что правый?) , Чтобы установить переменную члена (ов) соответственно - компилировать, ноstatic const MyPOD x;
не делает. Есть ли шанс, что это будет исправлено?Причина в том, что если у класса нет определяемого пользователем конструктора, то он может быть POD, а класс POD не инициализируется по умолчанию. Итак, если вы объявляете неинициализированный константный объект POD, какой в этом смысл? Поэтому я думаю, что Стандарт применяет это правило, чтобы объект действительно мог быть полезным.
Но если вы сделаете класс не-POD:
Обратите внимание на разницу между POD и не-POD.
Пользовательский конструктор - это один из способов сделать класс не-POD. Есть несколько способов сделать это.
Обратите внимание, что nonPOD_B не определяет пользовательский конструктор. Скомпилируйте это. Он скомпилирует:
И прокомментируйте виртуальную функцию, тогда она выдаст ошибку, как и ожидалось:
Ну, я думаю, вы неправильно поняли отрывок. Сначала он говорит следующее (§8.5 / 9):
Он говорит о не-POD-классе, возможно, о типе с квалификацией cv . То есть объект, не относящийся к POD, должен быть инициализирован по умолчанию, если не указан инициализатор. А что инициализировано по умолчанию ? Для не-POD спецификация говорит (§8.5 / 5),
Он просто говорит о конструкторе по умолчанию для T, независимо от того, не имеет значения его определяемый пользователем или созданный компилятором.
Если вы понимаете это, тогда поймите, что говорит спецификация дальше ((§8.5 / 9),
Таким образом, этот текст подразумевает, что программа будет неправильно сформирована, если объект имеет тип POD, квалифицированный как const , и не указан инициализатор (поскольку POD не инициализируются по умолчанию):
Между прочим, он компилируется нормально , потому что он не POD и может быть инициализирован по умолчанию .
источник
nonPOD_B
него нет конструктора по умолчанию, предоставленного пользователем, поэтому строкаconst nonPOD_B b2
не разрешена.B
в вопросе). Но в этом случае по-прежнему требуется конструктор по умолчанию, предоставляемый пользователем.const
инициализировать объект, не являющийся POD, путем вызова конструктора по умолчанию, созданного компилятором.Чистое предположение с моей стороны, но учтите, что другие типы тоже имеют подобное ограничение:
Таким образом, это правило не только согласовано, но и (рекурсивно) предотвращает единичные
const
(под) объекты:Что касается другой стороны вопроса (допуская это для типов с конструктором по умолчанию), я думаю, что идея состоит в том, что тип с конструктором по умолчанию, предоставляемым пользователем, должен всегда находиться в каком-то разумном состоянии после создания. Обратите внимание, что правила, как они есть, допускают следующее:
Тогда, возможно, мы могли бы сформулировать правило вроде «по крайней мере один член должен быть разумно инициализирован в конструкторе по умолчанию, предоставляемом пользователем», но это слишком много времени, потраченного на защиту от Мерфи. C ++ склонен доверять программисту в определенных вопросах.
источник
A(){}
ошибка исчезнет, поэтому ничего не мешает. Правило не работает рекурсивно -X(){}
в этом примере никогда не требуется.Я смотрел выступление Тимура Думлера на встрече C ++ 2018 и наконец понял, почему стандарт требует здесь конструктор, предоставляемый пользователем, а не просто объявленный пользователем. Это связано с правилами инициализации значений.
Рассмотрим два класса:
A
имеет конструктор, объявленный пользователем, и конструктор,B
предоставленный пользователем :На первый взгляд может показаться, что эти два конструктора будут вести себя одинаково. Но посмотрите, как инициализация значения ведет себя по-другому, тогда как только инициализация по умолчанию ведет себя так же:
A a;
инициализация по умолчанию: членint x
не инициализирован.B b;
инициализация по умолчанию: членint x
не инициализирован.A a{};
это значение инициализации: элементint x
имеет нулевой инициализируется .B b{};
инициализация значения: членint x
не инициализирован.Теперь посмотрим, что произойдет, когда мы добавим
const
:const A a;
это инициализация по умолчанию: это неправильно сформировано из-за правила, указанного в вопросе.const B b;
инициализация по умолчанию: членint x
не инициализирован.const A a{};
это значение инициализации: элементint x
имеет нулевой инициализируется .const B b{};
инициализация значения: членint x
не инициализирован.Неинициализированный
const
скаляр (например,int x
член) был бы бесполезен: запись в него неправильно сформирована (потому что этоconst
), а чтение из него - UB (потому что он содержит неопределенное значение). Таким образом, это правило не позволяет вам создать такую вещь, вынуждая вас либо добавить инициализатор, либо отказаться от опасного поведения, добавив предоставленный пользователем конструктор.Я думаю, было бы неплохо иметь атрибут,
[[uninitialized]]
сообщающий компилятору, когда вы намеренно не инициализируете объект. Тогда нам не пришлось бы делать наш класс не конструктивным по умолчанию, чтобы обойти этот угловой случай. Этот атрибут фактически был предложен , но, как и все другие стандартные атрибуты, он не требует какого-либо нормативного поведения, являясь просто подсказкой для компилятора.источник
Поздравляем, вы изобрели случай, в котором не требуется никакого определяемого пользователем конструктора для
const
объявления без инициализатора, чтобы иметь смысл.Можете ли вы предложить разумную новую формулировку правила, которое охватывает ваше дело, но при этом делает дела, которые должны быть незаконными, незаконными? Это меньше 5 или 6 абзацев? Легко и очевидно, как его применять в любой ситуации?
Я утверждаю, что придумать правило, позволяющее придать смысл созданному вами объявлению, действительно сложно, а еще сложнее убедиться, что это правило может применяться таким образом, чтобы это было понятно людям при чтении кода. Я бы предпочел несколько ограничительное правило, которое было бы правильным в большинстве случаев, чем очень тонкое и сложное правило, которое было трудно понять и применить.
Вопрос в том, есть ли веская причина, по которой правило должно быть более сложным? Есть ли код, который в противном случае было бы очень сложно написать или понять, который можно было бы написать намного проще, если бы правило было более сложным?
источник
const POD x;
незаконным, так же какconst int x;
и незаконным (что имеет смысл, потому что это бесполезно для POD), но сделало быconst NonPOD x;
законным (что имеет смысл, потому что у него могут быть подобъекты, содержащие полезные конструкторы / деструкторы, или сам полезный конструктор / деструктор) .struct NonPod { std::string s; }; const NonPod x;
Допускает, например, и выдает ошибку, когда стоит NonPodstruct NonPod { int i; std::string s; }; const NonPod x;