2010-01-29 18 views
48

sé que puedo hacer:En C++, ¿es posible reenviar declarar una clase como heredada de otra clase?

class Foo; 

pero puedo adelante declarar una clase que hereda de otra, como: referencia

class Bar {}; 

class Foo: public Bar; 

un caso de ejemplo de uso que se co-variante tipos de devolución

// somewhere.h 
class RA {} 
class RB : public RA {} 

... y luego en la otra cabecera que no incluye somewhere.h

// other.h 
class RA; 

class A { 
public: 
    virtual RA* Foo(); // this only needs the forward deceleration 
} 

class RB : public RA; // invalid but... 

class B { 
public: 
    virtual RB* Foo(); // 
} 

La única información que el compilador debe necesidad de procesar la declaración de RB* B:Foo() es que RB tiene RA como una clase base pública. Ahora claramente necesitaría algo en algún lugar. Si tiene la intención de desterrar los valores de devolución de Foo. Sin embargo, si algunos clientes nunca llaman al Foo, entonces no hay ninguna razón para que ellos incluyan somewhere.h, lo que podría acelerar significativamente la compilación.

+3

¿Por qué lo necesitarías? – Pace

+2

@Pace tipos de devolución de referencia co-variante. (Ver, editar.) Pero no estoy seguro de que las herencias múltiples no lo arruinen. – BCS

+0

Posible duplicado de [El puntero al tipo de clase incompleto no está permitido] (https://stackoverflow.com/questions/12027656/pointer-to-incomplete-class-type-is-not-allowed) – Archmede

Respuesta

33

Una declaración directa solo es realmente útil para decirle al compilador que existe una clase con ese nombre y será declarada y definida en otro lugar. No se puede usar en ningún caso donde el compilador necesite información contextual sobre la clase, ni sirve para nada al compilador para contarle solo un poco sobre la clase. (En general, solo puede usar la declaración forward cuando se refiera a esa clase sin otro contexto, por ejemplo, como parámetro o valor de retorno.)

Por lo tanto, no puede reenviar declarar Bar en ningún escenario donde luego lo use ayuda a declarar a Foo, y no tiene sentido tener una declaración directa que incluya la clase base. ¿Qué te dice eso además de nada?

+24

"¿Qué te dice eso? ¿además de nada? Uh, te dice que el tipo es una subclase de la clase base. Eso no es nada. Por ejemplo, si una función en su clase base tropieza con un objeto que contiene su subclase (como un "otro" objeto), no puede pasar ese objeto a una función que toma un objeto de clase base, porque no sabe cómo para convertir la subclase a la clase base, aunque puede acceder a todos los miembros públicos de la subclase. Estoy de acuerdo con usted en que es imposible hacer lo que esta persona pidió, pero estoy en desacuerdo con su evaluación de que hacerlo sería inútil. – codetaku

+0

@codetaku: Ha iniciado sesión para promocionar su comentario (: Para cualquiera que busque un ejemplo concreto, consulte la guía de estilo C++ de Google (en la sección "Contras"): https://google.github.io/styleguide/cppguide.html#Forward_Declarations – jwd

+0

@codetaku - ¡Sí! Por ejemplo, quiere un 'static_assert' o' enable_if' en un puntero inteligente para restringirlo a ciertos tipos; le gustaría usar el puntero inteligente en un tipo incompleto ... bam! – davidbak

34

Las declaraciones directas son declaraciones, no definiciones. Por lo tanto, cualquier cosa que requiera la declaración de una clase (como punteros a esa clase) solo necesita la declaración directa. Sin embargo, cualquier cosa que requiera la definición, es decir, necesitaría conocer la estructura real de la clase, no funcionaría solo con la declaración directa.

Las clases derivadas definitivamente necesitan conocer la estructura de sus padres, no solo que el padre existe, por lo que una declaración directa sería insuficiente.

+3

¿Necesita saber la estructura de una clase para manejar correctamente un puntero al mismo? Siempre que trates exclusivamente con referencias, estarás bien. – BCS

+2

@BCS Sí. Siempre que maneje punteros, solo necesita enviar declaraciones, pero cuando declara una clase derivada, necesita la definición del padre, no solo su declaración, porque no se trata solo de punteros. –

+4

Restringiría la implementación de objetos, pero creo que debería ser posible hacer una conversión '* D' ->' * B' solo conociendo la estructura de las herencias. Espero que se vea como las estructuras necesarias para implementar clases base virtuales. – BCS

1

No creo que sea útil. Considere lo siguiente: que haya definido una clase, Bar:

class Bar { 
public: 
    void frob(); 
}; 

Ahora se declara una clase Foo:

class Foo; 

Todo lo que se puede hacer con Foo es construir un puntero a ella. Ahora, suponga que agrega la información que se deriva de FooBar:

class Foo: public Bar; 

¿Qué se puede hacer ahora que no se podía hacer antes? Creo que todo lo que puede hacer es aceptar un puntero a Foo y convertirlo en un puntero a Bar, luego use ese puntero.

void frob(Foo* f) { 
    Bar *b = (Bar)f; 
    b->frob(); 
} 

Sin embargo, usted debe haber generado el puntero en otro lugar, por lo que podría haber aceptado simplemente un puntero a Bar lugar.

void frob(Bar* b) { 
    b->frob(); 
} 
+0

clase Foo: barra pública; dice que dynamic_cast <> funcionará, ¿no? Creo que esta sería una información importante. – Fabian

14

No, no es posible reenviar la herencia declarar, incluso si solo se trata de punteros. Cuando se trata de conversiones entre punteros, a veces el compilador tiene que conocer los detalles de la clase para hacer la conversión correctamente. Este es el caso de la herencia múltiple. (. Se podía caso especial algunas partes partes de la jerarquía que sólo utilizan la herencia simple, pero que no forma parte de la lengua)

Consideremos el siguiente caso trivial:

#include <stdio.h> 
class A { int x; }; 
class B { int y; }; 
class C: public A, public B { int z; }; 
void main() 
{ 
    C c; A *pa = &c; B *pb = &c; C *pc = &c; 
    printf("A: %p, B: %p, C: %p\n", pa, pb, pc); 
} 

La salida he recibido (el uso de 32 bits de visual Studio 2010), es la siguiente:

A: 0018F748, B: 0018F74C, C: 0018F748 

Así que para la herencia múltiple, cuando la conversión entre los punteros relacionados, el compilador tiene que insertar algún aritmética de punteros para obtener las conversiones derecha.

Esta es la razón por la que, incluso si se trata solo de punteros, no se puede reenviar para declarar la herencia.

En cuanto a por qué sería útil, mejoraría los tiempos de compilación cuando desee utilizar los tipos de retorno de covariante en lugar de usar moldes. Por ejemplo esto no va a compilar:

class RA; 
class A    { public: virtual RA *fooRet(); }; 
class RB; 
class B : public A { public: virtual RB *fooRet(); }; 

pero esto:

class RA; 
class A    { public: virtual RA *fooRet(); }; 
class RA { int x; }; 
class RB : public RA{ int y; }; 
class B : public A { public: virtual RB *fooRet(); }; 

Esto es útil cuando se tiene objetos de tipo B (no punteros o referencias). En este caso, el compilador es lo suficientemente inteligente como para utilizar una llamada de función directa, y puede utilizar el tipo de devolución de RB * directamente sin conversión. En este caso, generalmente sigo adelante y hago el retorno tipo RA * y hago un lanzamiento estático en el valor de retorno.

+6

Sé que esta es una respuesta anterior, pero esta es una respuesta mucho más útil que las más votadas y aceptadas, que ignoran el hecho de que convertir un 'Foo *' en una 'Barra *' (o referencia) puede ser útil incluso con un tipo incompleto. – EvanED

Cuestiones relacionadas