c++ - que - punteros a cadenas
¿Es posible subclasificar una estructura C en C++ y usar punteros a la estructura en código C? (10)
"Nunca se deriva de clases concretas." - Sutter
"Hacer que las clases que no sean hojas sean abstractas". - Meyers
Es simplemente incorrecto subclasificar clases que no son de interfaz. Deberías refactorizar tus bibliotecas.
Técnicamente, puede hacer lo que quiera, siempre que no invoque un comportamiento indefinido, por ejemplo, eliminando un puntero a la clase derivada mediante un puntero a su subobjeto de clase base. Ni siquiera necesita extern "C"
para el código C ++. Sí, es portátil. Pero es un diseño pobre
¿Hay algún efecto secundario al hacer esto?
Código C:
struct foo {
int k;
};
int ret_foo(const struct foo* f){
return f.k;
}
Código C ++:
class bar : public foo {
int my_bar() {
return ret_foo( (foo)this );
}
};
Hay una extern "C"
alrededor del código C ++ y cada código está dentro de su propia unidad de compilación.
¿Es portátil entre los compiladores?
Ciertamente, no recomiendo usar tales subclases raras. Sería mejor cambiar su diseño para usar composición en lugar de herencia. Solo haz un miembro
foo * m_pfoo;
en la clase de bar y hará el mismo trabajo.
Otra cosa que puedes hacer es crear una clase más de FooWrapper, que contenga la estructura en sí misma con el método getter correspondiente. Luego puede subclasificar el contenedor. De esta forma, el problema con el destructor virtual se ha ido.
Esto es completamente legal. En C ++, las clases y las estructuras son conceptos idénticos, con la excepción de que todos los miembros de la estructura son públicos por defecto. Esa es la única diferencia. Entonces, preguntar si puedes extender una estructura no es diferente a preguntar si puedes extender una clase.
Hay una advertencia aquí. No hay garantía de coherencia de diseño del compilador al compilador. Por lo tanto, si compila su código C con un compilador diferente de su código C ++, puede tener problemas relacionados con el diseño del miembro (especialmente relleno). Esto incluso puede ocurrir al usar compiladores C y C ++ del mismo proveedor.
He tenido que pasar esto con gcc y g ++. Trabajé en un proyecto que usó varias estructuras grandes. Desafortunadamente, g ++ empaquetó las estructuras significativamente más flojo que gcc, lo que causó problemas significativos al compartir objetos entre C y C ++. Finalmente tuvimos que configurar manualmente el relleno e insertar el relleno para que el código C y C ++ tratara las estructuras de la misma manera. Sin embargo, tenga en cuenta que este problema puede ocurrir independientemente de la creación de subclases. De hecho, no estábamos subclasificando la estructura C en este caso.
Esto es perfectamente legal, aunque podría ser confuso para otros programadores.
Puede usar la herencia para extender las estructuras C con métodos y constructores.
Muestra:
struct POINT { int x, y; }
class CPoint : POINT
{
public:
CPoint( int x_, int y_ ) { x = x_; y = y_; }
const CPoint& operator+=( const POINT& op2 )
{ x += op2.x; y += op2.y; return *this; }
// etc.
};
Extender las estructuras puede ser "más" malo, pero no es algo que se te prohíba hacer.
Esto es perfectamente legal, y puede verlo en la práctica con las clases MFC CRect y CPoint. CPoint se deriva de POINT (definido en windef.h) y CRect deriva de RECT. Simplemente está decorando un objeto con funciones de miembro. Siempre y cuando no extiendas el objeto con más datos, estarás bien. De hecho, si tiene una estructura compleja de C que es difícil inicializar por defecto, extenderla con una clase que contenga un constructor predeterminado es una manera fácil de manejar ese problema.
Incluso si haces esto:
foo *pFoo = new bar;
delete pFoo;
entonces estás bien, ya que tu constructor y destructor son triviales, y no has asignado ninguna memoria extra.
Tampoco tiene que envolver su objeto C ++ con C '''' externo '''', ya que no está pasando realmente un tipo C ++ a las funciones C.
Funcionará, y de forma portátil PERO no puede usar ninguna función virtual (que incluye destructores).
Yo recomendaría que en lugar de hacer esto, tuvieras Bar conteniendo un Foo.
class Bar
{
private:
Foo mFoo;
};
No creo que sea necesariamente un problema. El comportamiento está bien definido, y siempre que tenga cuidado con los problemas de por vida (no mezcle y combine las asignaciones entre el código C ++ y C) hará lo que quiera. Debería ser perfectamente portátil entre los compiladores.
El problema con los destructores es real, pero se aplica cada vez que el destructor de la clase base no es virtual, no solo para las estructuras C. Es algo que debe tener en cuenta pero que no excluye el uso de este patrón.
No entiendo por qué no simplemente haces que ret_foo sea un método miembro. Tu forma actual hace que tu código sea tremendamente difícil de entender. ¿Qué tiene de difícil utilizar una clase real en primer lugar con una variable miembro y métodos get / set?
Sé que es posible subclasificar estructuras en C ++, pero el peligro es que otros no puedan entender lo que codificaste porque es muy raro que alguien realmente lo haga. En su lugar, buscaría una solución sólida y común.
Wow, eso es malo.
¿Es portátil entre los compiladores?
Definitivamente no. Considera lo siguiente:
foo* x = new bar();
delete x;
Para que esto funcione, el destructor de foo debe ser virtual, lo que claramente no es. Siempre y cuando no uses new
y siempre que el objectd derivado no tenga destructores personalizados, podrías tener suerte.
/ EDITAR: Por otro lado, si el código solo se usa como en la pregunta, la herencia no tiene ninguna ventaja sobre la composición. Solo sigue los consejos dados por m_pGladiator.
Probablemente funcionará, pero no creo que esté garantizado. La siguiente es una cita de ISO C ++ 10/5:
Un subobjeto de clase base puede tener un diseño (3.7) diferente del diseño de un objeto más derivado del mismo tipo.
Es difícil ver cómo en el "mundo real" podría ser así.
EDITAR:
La conclusión es que el estándar no ha limitado el número de lugares donde un diseño de subobjetos de clase base puede ser diferente de un objeto concreto con ese mismo tipo de Base. El resultado es que cualquier suposición que pueda tener, como POD-ness, etc., no es necesariamente cierta para el subobjeto de clase base.
EDITAR:
Un enfoque alternativo, y uno cuyo comportamiento está bien definido, es convertir a ''foo'' en miembro de ''barra'' y proporcionar un operador de conversión donde sea necesario.
class bar {
public:
int my_bar() {
return ret_foo( foo_ );
}
//
// This allows a ''bar'' to be used where a ''foo'' is expected
inline operator foo& () {
return foo_;
}
private:
foo foo_;
};