c++ - polimorfismo - herencia programacion
C++ problema de herencia virtual privada (3)
En el siguiente código, parece que la clase C no tiene acceso al constructor de A, que es necesario debido a la herencia virtual. Sin embargo, el código todavía se compila y ejecuta. ¿Por qué funciona?
class A {};
class B: private virtual A {};
class C: public B {};
int main() {
C c;
return 0;
}
Además, si elimino el constructor predeterminado de A, por ejemplo
class A {
public:
A(int) {}
};
class B: private virtual A {
public:
B() : A(3) {}
};
entonces
class C: public B {};
compilaría (inesperadamente), pero
class C: public B {
public:
C() {}
};
No compilaría, como se esperaba.
Código compilado con "g ++ (GCC) 3.4.4 (cygming especial, gdc 0.12, usando dmd 0.125)", pero se ha verificado que se comporta igual con otros compiladores.
De acuerdo con C ++ Core Issue # 7, la clase con una base privada virtual no se puede derivar de. Este es un error en el compilador.
Las clases base virtuales siempre se inicializan desde la mayoría de las clases derivadas (C aquí). El compilador debe verificar que el constructor sea accesible (es decir, me aparece un error con g ++ 3.4 para
class A { public: A(int) {} };
class B: private virtual A {public: B() : A(0) {} };
class C: public B {};
int main() {
C c;
return 0;
}
mientras que su descripción implica que no hay ninguna) pero el hecho de que como base, A sea privada o no importe (subvertir sería fácil: class C: public B, private virtual A
).
La razón por la que se llama a los constructores de clases de base virtual desde la clase más derivada es que debe construirse antes que cualquier clase que los tenga como clase base.
Edición: Kirill mencionó un viejo problema central que está en desacuerdo con mi lectura y el comportamiento de los compiladores recientes. Trataré de obtener referencias estándar de una forma u otra, pero eso puede llevar tiempo.
Para la segunda pregunta, es probablemente porque no hace que se defina implícitamente. Si el constructor simplemente se declara implícitamente, no hay error. Ejemplo:
struct A { A(int); };
struct B : A { };
// goes fine up to here
// not anymore: default constructor now is implicitly defined
// (because it''s used)
B b;
Para su primera pregunta, depende del nombre que use el compilador. No tengo idea de lo que especifica el estándar, pero este código, por ejemplo, es correcto porque el nombre de la clase externa (en lugar del nombre de la clase heredado) es accesible:
class A {};
class B: private virtual A {};
class C: public B { C(): ::A() { } }; // don''t use B::A
Tal vez el estándar está subespecificado en este punto. Tendremos que mirar.
No parece haber ningún problema con el código. Además, hay indicios de que el código es válido. El subobjeto de clase base (virtual) está inicializado por defecto: no hay texto que implique que la búsqueda de nombres para el nombre de la clase se encuentre dentro del alcance de C
Esto es lo que dice la Norma:
12.6.2/8
(C ++ 0x)
Si un miembro de datos no estático o una clase base no se nombra con un id-inicializador-mem (incluido el caso en el que no hay una lista-inicializador-mem porque el constructor no tiene inicializador-ctor) y la entidad no es un virtual clase base de una clase abstracta
[...] de lo contrario, la entidad está inicializada por defecto
Y C ++ 03 tiene un texto similar (usted es un texto menos claro; simplemente dice que su constructor predeterminado se llama en un lugar y en otro depende de si la clase es un POD). Para que el compilador inicie por defecto el subobjeto, solo tiene que llamar a su constructor predeterminado; no es necesario buscar primero el nombre de la clase base ( ya sabe qué base se considera).
Considere este código que ciertamente pretende ser válido, pero que fallaría si se hiciera esto (vea 12.6.2/4
en C ++ 0x)
struct A { };
struct B : virtual A { };
struct C : B, A { };
C c;
Si el constructor predeterminado del compilador simplemente buscara el nombre de clase A
dentro de C
, tendría un resultado de búsqueda ambiguo con respecto a qué subobjeto inicializar, porque se encuentran los nombres de clase no virtuales A
y virtuales A
. Si se pretende que su código esté mal formado, diría que la Norma ciertamente necesita ser aclarada.
Para el constructor, note lo que dice 12.4/6
sobre el destructor de C
:
Se llama a todos los destructores como si estuvieran referenciados con un nombre calificado, es decir, ignorando cualquier posible destructor de reemplazo virtual en más clases derivadas.
Esto se puede interpretar de dos maneras:
- llamando A :: ~ A ()
- llamando :: A :: ~ A ()
Me parece que el Estándar es menos claro aquí. La segunda manera lo haría válido (por 3.4.3/6
, C ++ 0x, porque los nombres de clase A
se buscan en el alcance global), mientras que el primero lo hará inválido (porque ambos A
encontrarán la clase heredada nombres). También depende con qué subobjeto comience la búsqueda (y creo que tendremos que usar el subobjeto de la clase base virtual como punto de inicio). Si esto va como
virtual_base -> A::~A();
Luego, encontraremos directamente el nombre de la clase de la base virtual como un nombre público, ya que no tendremos que pasar por los ámbitos de la clase derivada y encontrar el nombre como no accesible. Una vez más, el razonamiento es similar. Considerar:
struct A { };
struct B : A { };
struct C : B, A {
} c;
Si el destructor simplemente llamara a this->A::~A()
, esta llamada no sería válida debido al resultado de búsqueda ambiguo de A
como un nombre de clase heredado (no puede referirse a ninguna función miembro no estática del objeto de clase base directa del ámbito C
, ver 10.1/3
, C ++ 03). Solo tendrá que identificar los nombres de clase que están involucrados, y debe comenzar con la referencia de subobjeto de la clase como a_subobject->::A::~A();
.