Когда частный конструктор не является частным конструктором?

Скажем, у меня есть тип, и я хочу, чтобы его конструктор по умолчанию был закрыт. Я пишу следующее:

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;