tipos ser puede privados privado polimorfismo parametrizado invocar herencia heredada constructores como clase c++ c++11 language-lawyer default-constructor aggregate-initialization

c++ - ser - ¿Cuándo un constructor privado no es un constructor privado?



tipos de constructores (3)

El truco está en C ++ 14 8.4.2 / 5 [dcl.fct.def.default]:

... Una función es proporcionada por el usuario si es declarada por el usuario y no está explícitamente predeterminada o eliminada en su primera declaración. ...

Lo que significa que el constructor predeterminado de C realidad no es proporcionado por el usuario, porque se omitió explícitamente en su primera declaración. Como tal, C no tiene constructores proporcionados por el usuario y, por lo tanto, es un agregado por 8.5.1 / 1 [dcl.init.aggr]:

Un agregado es una matriz o una clase (Cláusula 9) sin constructores proporcionados por el usuario (12.1), sin miembros de datos no estáticos privados o protegidos (Cláusula 11), sin clases base (Cláusula 10) y sin funciones virtuales (10.3 )

Digamos que tengo un tipo y quiero que su constructor predeterminado sea privado. Escribo lo siguiente:

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 }

Excelente.

Pero entonces, el constructor resulta no ser tan privado como pensé que era:

class C { C() = default; }; int main() { C c{}; // OK on all compilers auto c2 = C{}; // OK on all compilers }

Esto me parece un comportamiento muy sorprendente, inesperado y explícitamente indeseable. ¿Por qué está bien esto?


No está llamando al constructor predeterminado, está utilizando la inicialización agregada en un tipo agregado. Los tipos agregados pueden tener un constructor predeterminado, siempre que sea predeterminado donde se declara por primera vez:

Desde [dcl.init.aggr]/1 :

Un agregado es una matriz o una clase (Cláusula [clase]) con

  • sin constructores proporcionados por el usuario ([class.ctor]) (incluidos los heredados ([namespace.udecl]) de una clase base),
  • sin miembros de datos no estáticos privados o protegidos (Cláusula [class.access]),
  • sin funciones virtuales ([class.virtual]), y
  • no hay clases base virtuales, privadas o protegidas ([class.mi]).

y de [dcl.fct.def.default]/5

Las funciones predeterminadas explícitamente y las funciones declaradas implícitamente se denominan colectivamente funciones predeterminadas, y la implementación proporcionará definiciones implícitas para ellas ([class.ctor] [class.dtor], [class.copy]), lo que podría significar definirlas como eliminadas . Una función es proporcionada por el usuario si es declarada por el usuario y no está explícitamente predeterminada o eliminada en su primera declaración. Una función explícitamente predeterminada por el usuario (es decir, explícitamente predeterminada después de su primera declaración) se define en el punto donde está explícitamente predeterminada; si dicha función se define implícitamente como eliminada, el programa está mal formado. [Nota: Declarar una función como predeterminada después de su primera declaración puede proporcionar una ejecución eficiente y una definición concisa al tiempo que permite una interfaz binaria estable a una base de código en evolución. - nota final]

Por lo tanto, nuestros requisitos para un agregado son:

  • no miembros no públicos
  • sin funciones virtuales
  • no hay clases base virtuales o no públicas
  • no se heredan constructores proporcionados por el usuario o de otra manera, lo que permite solo constructores que son:
    • declarado implícitamente, o
    • declarado explícitamente y definido como predeterminado al mismo tiempo.

C cumple con todos estos requisitos.

Naturalmente, puede deshacerse de este comportamiento de construcción predeterminado falso simplemente proporcionando un constructor predeterminado vacío o definiendo el constructor como predeterminado después de declararlo:

class C { C(){} }; // --or-- class C { C(); }; inline C::C() = default;


Las respuestas de Angew y jaggedSpire son excelentes y se aplican a c ++ 11 . Y c ++ 14 . Y c ++ 17 .

Sin embargo, en c ++ 20 , las cosas cambian un poco y el ejemplo en el OP ya no se compilará:

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

Como se señaló en las dos respuestas, la razón por la que funcionan las dos últimas declaraciones es porque C es un agregado y esto es una inicialización agregada. Sin embargo, como resultado de P1008 (usando un ejemplo motivador no muy diferente del OP), la definición de cambios agregados en C ++ 20 a, desde [dcl.init.aggr]/1 :

Un agregado es una matriz o una clase ([clase]) con

  • sin constructores heredados o declarados por el usuario ([class.ctor]),
  • sin miembros de datos no estáticos directos privados o protegidos ([class.access]),
  • sin funciones virtuales ([class.virtual]), y
  • no hay clases base virtuales, privadas o protegidas ([class.mi]).

El énfasis es mío. Ahora el requisito no es constructores declarados por el usuario , mientras que solía serlo (como ambos usuarios citan en sus respuestas y pueden verse históricamente para C ++ 11 , C ++ 14 y C ++ 17 ) sin constructores proporcionados por el usuario . El constructor predeterminado para C es declarado por el usuario, pero no proporcionado por el usuario, y por lo tanto deja de ser un agregado en C ++ 20.

Aquí hay otro ejemplo ilustrativo de cambios agregados:

class A { protected: A() { }; }; struct B : A { B() = default; }; auto x = B{};

B no era un agregado en C ++ 11 o C ++ 14 porque tiene una clase base. Como resultado, B{} simplemente invoca el constructor predeterminado (declarado por el usuario pero no proporcionado por el usuario), que tiene acceso al constructor predeterminado protegido de A

En C ++ 17, como resultado de P0017 , los agregados se ampliaron para permitir clases base. B es un agregado en C ++ 17, lo que significa que B{} es una inicialización agregada que tiene que inicializar todos los subobjetos, incluido el subobjeto A Pero debido a que el constructor predeterminado de A está protegido, no tenemos acceso a él, por lo que esta inicialización está mal formada.

En C ++ 20, debido al constructor declarado por el usuario de B , nuevamente deja de ser un agregado, por lo que B{} vuelve a invocar el constructor predeterminado y esto es nuevamente una inicialización bien formada.