¿Cuándo un constructor privado no es un constructor privado?


Digamos que tengo un tipo y quiero hacer privado su constructor predeterminado. 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
}

Genial.

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 no deseado. ¿Por qué esto está bien?

Author: curiousguy, 2016-06-03

2 answers

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

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

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

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

 56
Author: Angew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-03 15:35:43

No estás llamando al constructor predeterminado, estás usando la inicialización de aggregate en un tipo aggregate. A los tipos Aggregate se les permite tener un constructor predeterminado, siempre y cuando esté predeterminado donde se declaró primero:

De [dcl.init.aggr]/1:

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

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

Y de [dcl.fct.def.predeterminado]/5

Las funciones explícitamente predeterminadas y las funciones implícitamente declaradas se denominan colectivamente funciones predeterminadas, y la implementación proporcionará definiciones implícitas para ellas ([class.ctor] [clase.dtor], [clase.copy]), lo que podría significar definirlos como eliminados. Una función es proporcionada por el usuario si está declarada por el usuario y no está explícitamente predeterminada o eliminada en su primera declaración. Una función predeterminada explícitamente proporcionada por el usuario (es decir, predeterminada explícitamente después de su primera declaración) se define en el punto donde está predeterminada explícitamente; si dicha función se define implícitamente como eliminada, el programa está mal formado. [Nota: Declarar una función como predeterminada después 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 hay miembros no públicos
  • sin funciones virtuales
  • no hay clases base virtuales o no públicas
  • no hay constructores proporcionados por el usuario heredados o de otra manera, lo que permite solo los constructores que son:
    • implícitamente declarado, o
    • explícitamente declarado y definido como predeterminado al mismo tiempo.

C cumple todos estos requisitos.

Naturalmente, puede deshacerse de este comportamiento de construcción false default 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;
 49
Author: jaggedSpire,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-31 16:49:01