Допустим, у меня есть тип, и я хочу сделать его конструктор по умолчанию закрытым. Я пишу следующее:
class C {
C() = default;
};
int main() {
C c; // error: C::C() is private within this context (g++)
// error: calling a private constructor of class 'C' (clang++)
// error C2248: 'C::C' cannot access private member declared in class 'C' (MSVC)
auto c2 = C(); // error: as above
}
Отлично.
Но потом конструктор оказывается не таким частным, как я думал:
class C {
C() = default;
};
int main() {
C c{}; // OK on all compilers
auto c2 = C{}; // OK on all compilers
}
Это кажется мне очень удивительным, неожиданным и явно нежелательным поведением. Почему это нормально?
C c{};
агрегатная инициализация не вызывается без вызова конструктора?C
есть агрегат.=default
клиент, это было бы более разумно. Но частный=default
ctor кажется важной вещью, которую нельзя игнорировать. Болееclass C { C(); } inline C::C()=default;
того, это несколько удивительно.Ответы:
Уловка в C ++ 14 8.4.2 / 5 [dcl.fct.def.default]:
Это означает, что
C
конструктор по умолчанию на самом деле не предоставляется пользователем, потому что он был явно задан по умолчанию при первом объявлении. Таким образом,C
не имеет конструкторов, предоставляемых пользователем, и поэтому является агрегатом для 8.5.1 / 1 [dcl.init.aggr]:источник
C{}
работает, даже если конструкторdelete
d.Вы не вызываете конструктор по умолчанию, вы используете агрегатную инициализацию для агрегатного типа. Агрегатным типам разрешено иметь конструктор по умолчанию, если он используется по умолчанию там, где он был впервые объявлен:
Из [dcl.init.aggr] / 1 :
и из [dcl.fct.def.default] / 5
Таким образом, наши требования к агрегату:
C
выполняет все эти требования.Естественно, вы можете избавиться от этого ложного поведения конструкции по умолчанию, просто предоставив пустой конструктор по умолчанию или определив конструктор по умолчанию после его объявления:
class C { C(){} }; // --or-- class C { C(); }; inline C::C() = default;
источник
Ответы Angew и jaggedSpire превосходны и применимы кc ++ 11. А такжеc ++ 14. А такжеc ++ 17.
Однако в c ++ 20, все немного изменится, и пример в OP больше не будет компилироваться:
class C { C() = default; }; C p; // always error auto q = C(); // always error C r{}; // ok on C++11 thru C++17, error on C++20 auto s = C{}; // ok on C++11 thru C++17, error on C++20
Как указано в двух ответах, причина, по которой последние два объявления работают, заключается в том, что
C
это агрегат, а это агрегат-инициализация. Однако в результате P1008 (используя мотивирующий пример, не слишком отличающийся от OP) определение агрегата в C ++ 20 изменяется на [dcl.init.aggr] / 1 :Акцент мой. Теперь требование не заключается в конструкторах, объявленных пользователем , тогда как раньше (как оба пользователя цитируют в своих ответах и могут быть просмотрены исторически для C ++ 11 , C ++ 14 и C ++ 17 ) не было конструкторов, предоставляемых пользователем. . Конструктор по умолчанию для
C
объявлен пользователем, но не предоставляется пользователем и, следовательно, перестает быть агрегатом в C ++ 20.Вот еще один наглядный пример совокупных изменений:
class A { protected: A() { }; }; struct B : A { B() = default; }; auto x = B{};
B
не был агрегатом в C ++ 11 или C ++ 14, потому что имеет базовый класс. В результатеB{}
просто вызывает конструктор по умолчанию (объявленный пользователем, но не предоставленный пользователем), который имеет доступ кA
защищенному конструктору по умолчанию.В C ++ 17 в результате P0017 агрегаты были расширены для поддержки базовых классов.
B
является агрегатом в C ++ 17, что означает, чтоB{}
это агрегат-инициализация, которая должна инициализировать все подобъекты, включаяA
подобъект. Но посколькуA
конструктор по умолчанию защищен, у нас нет доступа к нему, поэтому эта инициализация плохо сформирована.В C ++ 20 из-за
B
конструктора, объявленного пользователем, он снова перестает быть агрегатом, поэтомуB{}
возвращается к вызову конструктора по умолчанию, и это снова правильно сформированная инициализация.источник