¿Es posible subclasificar una estructura C en C++ y usar punteros a la estructura en código C?

StackOverflow https://stackoverflow.com/questions/127290

  •  02-07-2019
  •  | 
  •  

Pregunta

¿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 un extern "C" alrededor del código C++ y cada código está dentro de su propia unidad de compilación.

¿Es esto portátil entre compiladores?

¿Fue útil?

Solución

Esto es totalmente 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 de forma predeterminada.Esa es la única diferencia.Entonces, preguntar si se puede extender una estructura no es diferente a preguntar si se puede extender una clase.

Hay una advertencia aquí.Hay sin garantía de coherencia del diseño de un compilador a otro.Entonces, si compila su código C con un compilador diferente al de su código C++, puede tener problemas relacionados con el diseño de los miembros (especialmente el relleno).Esto puede ocurrir incluso cuando se utilizan compiladores C y C++ del mismo proveedor.

I tener Esto sucedió con gcc y g++.Trabajé en un proyecto que utilizaba varias estructuras grandes.Desafortunadamente, g++ empaquetó las estructuras significativamente más flexibles que gcc, lo que causó problemas significativos al compartir objetos entre el código C y C++.Eventualmente tuvimos que configurar manualmente el embalaje e insertar el relleno para que el código C y C++ trate las estructuras de la misma manera.Sin embargo, tenga en cuenta que este problema puede ocurrir independientemente de la subclasificación.De hecho, en este caso no estábamos subclasificando la estructura C.

Otros consejos

Ciertamente no recomiendo usar subclases tan extrañas.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 barra y hará el mismo trabajo.

Otra cosa que puedes hacer es crear una clase más FooWrapper, que contenga la estructura en sí misma con el método getter correspondiente.Luego puedes subclasificar el contenedor.De esta forma desaparece el problema con el destructor virtual.

"Nunca deriva de clases de concreto". - Sutter

"Haga que las clases no hojas resumen". - Meyers

Es simplemente incorrecto subclasificar clases que no son de interfaz.Deberías refactorizar tus bibliotecas.

Técnicamente, puedes hacer lo que quieras, siempre y cuando no invoques un comportamiento indefinido, por ejemplo.ej., eliminar un puntero a la clase derivada mediante un puntero a su subobjeto de clase base.Ni siquiera necesitas extern "C" para el código C++.Sí, es portátil.Pero es un mal diseño.

Esto es perfectamente legal, aunque puede resultar confuso para otros programadores.

Puede utilizar la herencia para ampliar 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 estructuras puede ser "más" malvado, pero no es algo que esté prohibido hacer.

Vaya, eso es malvado.

¿Es esto portátil entre compiladores?

Definitivamente no.Considera lo siguiente:

foo* x = new bar();
delete x;

Para que esto funcione, el destructor de foo debe ser virtual, lo cual claramente no lo es.Mientras no uses new y siempre que el objeto derivado no tenga destructores personalizados, podría tener suerte.

/EDITAR:Por otro lado, si el código sólo se utiliza como en la pregunta, la herencia no tiene ninguna ventaja sobre la composición.Simplemente sigue los consejos dados por m_pGladiator.

Esto es perfectamente legal y puede verlo en la práctica con las clases MFC CRect y CPoint.CPoint deriva de POINT (definido en windef.h) y CRect deriva de RECT.Simplemente estás decorando un objeto con funciones miembro.Siempre que no extienda el objeto con más datos, estará bien.De hecho, si tiene una estructura C compleja cuya inicialización predeterminada es difícil, extenderla con una clase que contenga un constructor predeterminado es una forma fácil de solucionar 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 memoria adicional.

Tampoco es necesario que envuelva su objeto C++ con "C" externo, ya que en realidad no está pasando un objeto C++. tipo a las funciones C.

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 ni combine asignaciones entre el código C++ y C), hará lo que desee.Debería ser perfectamente portátil entre compiladores.

El problema con los destructores es real, pero se aplica siempre que el destructor de la clase base no sea virtual, no solo para las estructuras C.Es algo que debe tener en cuenta, pero no impide utilizar este patrón.

Funcionará y de forma portátil, PERO no podrá utilizar ninguna función virtual (lo que incluye destructores).

Recomendaría que en lugar de hacer esto, Bar contenga un Foo.

class Bar
{
private:
   Foo  mFoo;
};

No entiendo por qué no simplemente conviertes a ret_foo en un método miembro.Su forma actual hace que su código sea tremendamente difícil de entender.¿Qué tiene de difícil usar 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 podrán entender lo que codificaste porque es muy raro que alguien realmente lo haga.En su lugar, optaría por una solución sólida y común.

Probablemente funcionará, pero no creo que esté garantizado.La siguiente es una cita de ISO C++ 10/5:

Un subobjeto de clase base podría 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" esto podría ser así.

EDITAR:

La conclusión es que el estándar no ha limitado el número de lugares donde el diseño de un subobjeto 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, etc.no son necesariamente ciertos para el subobjeto de la clase base.

EDITAR:

Un enfoque alternativo, y cuyo comportamiento está bien definido, es hacer que 'foo' sea miembro de 'bar' y proporcionar un operador de conversión cuando 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_;
};
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top