Когда частный конструктор не является частным конструктором?
Скажем, у меня есть тип, и я хочу, чтобы его конструктор по умолчанию был закрыт. Я пишу следующее:
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
}
Это поражает меня как очень неожиданное, неожиданное и явно нежелательное поведение. Почему это нормально?
Ответы
Ответ 1
Трюк находится в С++ 14 8.4.2/5 [dcl.fct.def.default]:
... Функция предоставляется пользователем, если она объявлена пользователем и явно не выполнена по умолчанию или удалены по его первой декларации....
Это означает, что конструктор C
по умолчанию на самом деле не предоставлен пользователем, поскольку он был явно дефолт по его первой декларации. Таким образом, C
не имеет конструкторов, предоставляемых пользователем, и поэтому является агрегатом для 8.5.1/1 [dcl.init.aggr]:
Агрегат - это массив или класс (раздел 9) без конструкторов, предоставляемых пользователем (12.1), без личных или защищенные нестатические элементы данных (раздел 11), базовые классы (раздел 10) и виртуальные функции (10.3).
Ответ 2
Вы не вызываете конструктор по умолчанию, вы используете агрегатную инициализацию по агрегированному типу. Агрегатным типам разрешено иметь конструктор с невыполнением обязательств, если он по умолчанию не был объявлен:
От [dcl.init.aggr]/1:
Агрегат - это массив или класс (раздел [класс]) с
- нет конструкторов, предоставляемых пользователем ([class.ctor]) (включая унаследованные ([namespace.udecl]) из базового класса),
- нет частных или защищенных нестатических элементов данных (Clause [class.access]),
- нет виртуальных функций ([class.virtual]) и
- нет виртуальных, закрытых или защищенных базовых классов ([class.mi]).
и [dcl.fct.def.default]/5
Явно-дефолтные функции и неявно объявленные функции коллективно называются дефолтными функциями, и реализация должна предоставлять им неявные определения ([class.ctor] [class.dtor], [class.copy]), что может означать определение их удалить. Функция предоставляется пользователю, если она объявлена пользователем и явно не дефолтна или не удалена в ее первом объявлении. Определенная пользователем функция явно дефолт (т.е. явно дефолт после ее первого объявления) определена в точке, где он явно дефолт; если такая функция неявно определена как удаленная, программа плохо сформирована. [Примечание. Объявление функции по умолчанию после ее первого объявления может обеспечить эффективное выполнение и краткое определение, позволяя стабильному двоичному интерфейсу развиваться база кода. - конечная нота]
Таким образом, наши требования к совокупности:
- нет непубличных участников
- нет виртуальных функций
- нет виртуальных или непубличных базовых классов
- не предоставленные пользователем конструкторы, унаследованные или иным образом, которые допускают только конструкторы, которые:
- неявно объявлено, или
- явно объявлено и определено как дефолт одновременно.
C
выполняет все эти требования.
Естественно, вы можете избавиться от этого ложного поведения по умолчанию, просто предоставив пустой конструктор по умолчанию или указав конструктор по умолчанию после объявления:
class C {
C(){}
};
// --or--
class C {
C();
};
inline C::C() = default;