È possibile sottoclassare una struttura C in C ++ e utilizzare i puntatori alla struttura in codice C?

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

  •  02-07-2019
  •  | 
  •  

Domanda

C'è un effetto collaterale nel fare questo:

Codice C:

struct foo {
      int k;
};

int ret_foo(const struct foo* f){ 
    return f.k; 
}

Codice C ++:

class bar : public foo {

   int my_bar() { 
       return ret_foo( (foo)this ); 
   }

};

C'è un extern " C " attorno al codice C ++ e ogni codice è all'interno della propria unità di compilazione.

È portatile su tutti i compilatori?

È stato utile?

Soluzione

Questo è del tutto legale. In C ++, le classi e le strutture sono concetti identici, con l'eccezione che tutti i membri della struttura sono pubblici per impostazione predefinita. Questa è l'unica differenza. Pertanto, chiedere se è possibile estendere una struttura non è diverso dal chiedere se è possibile estendere una classe.

C'è un avvertimento qui. Non esiste nessuna garanzia sulla coerenza del layout dal compilatore al compilatore. Quindi, se compili il tuo codice C con un compilatore diverso dal tuo codice C ++, potresti riscontrare problemi relativi al layout dei membri (in particolare il riempimento). Ciò può verificarsi anche quando si usano compilatori C e C ++ dello stesso fornitore.

I ho è successo con gcc e g ++. Ho lavorato a un progetto che utilizzava diverse strutture di grandi dimensioni. Sfortunatamente, g ++ ha compresso le strutture in modo significativamente più libero di gcc, il che ha causato problemi significativi nella condivisione di oggetti tra codice C e C ++. Alla fine abbiamo dovuto impostare manualmente l'imballaggio e inserire l'imbottitura per far sì che il codice C e C ++ trattasse le strutture allo stesso modo. Si noti tuttavia che questo problema può verificarsi indipendentemente dalla sottoclasse. In realtà non stavamo sottoclassando la struttura C in questo caso.

Altri suggerimenti

Sicuramente non consiglio di usare una sottoclasse così strana. Sarebbe meglio cambiare il tuo design per usare la composizione anziché l'eredità. Crea un solo membro

  

foo * m_pfoo;

nella classe bar e farà lo stesso lavoro.

Un'altra cosa che puoi fare è creare un FooWrapper di classe superiore, contenente la struttura in sé con il metodo getter corrispondente. Quindi è possibile sottoclassare il wrapper. In questo modo il problema con il distruttore virtuale è scomparso.

  

& # 8220; Non derivare mai da classi concrete. & # 8221; & # 8212; Sutter

     

& # 8220; Rendi astratte le classi non foglia. & # 8221; & # 8212; Meyers

È semplicemente sbagliato sottoclassare le classi non di interfaccia. Dovresti refactoring le tue librerie.

Tecnicamente, puoi fare quello che vuoi, purché tu non invochi comportamenti indefiniti di, e. g., eliminando un puntatore alla classe derivata da un puntatore al suo oggetto secondario della classe base. Non hai nemmeno bisogno di extern " C " per il codice C ++. Sì, è portatile. Ma è un design scadente.

Questo è perfettamente legale, anche se potrebbe essere fonte di confusione per altri programmatori.

È possibile utilizzare l'ereditarietà per estendere le strutture C con metodi e costruttori.

Esempio:

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.
};

L'estensione delle strutture potrebbe essere "più" male, ma non è qualcosa che ti è proibito fare.

Wow, questo è male.

  

È portatile su tutti i compilatori?

Sicuramente no. Considera quanto segue:

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

Affinché ciò funzioni, il distruttore di foo deve essere virtuale, cosa che chiaramente non lo è. Fintanto che non usi nuovo e finché l'oggetto derivato non ha distruttori personalizzati, potresti essere fortunato.

/ EDIT: D'altra parte, se il codice viene utilizzato solo come nella domanda, l'ereditarietà non ha alcun vantaggio rispetto alla composizione. Segui i consigli di m_pGladiator.

Questo è perfettamente legale e puoi vederlo in pratica con le classi MFC CRect e CPoint. CPoint deriva da POINT (definito in windef.h) e CRect deriva da RECT. Stai semplicemente decorando un oggetto con le funzioni membro. Finché non estendi l'oggetto con più dati, stai bene. In effetti, se hai una complessa struttura a C che è una seccatura da inizializzare in modo predefinito, estenderla con una classe che contiene un costruttore predefinito è un modo semplice per affrontare questo problema.

Anche se lo fai:

foo *pFoo = new bar;
delete pFoo;

allora stai bene, dato che il tuo costruttore e distruttore sono banali e non hai allocato memoria aggiuntiva.

Inoltre non è necessario racchiudere l'oggetto C ++ con 'extern " C "', poiché in realtà non si passa un tipo C ++ alle funzioni C.

Non penso che sia necessariamente un problema. Il comportamento è ben definito e fintanto che stai attento ai problemi della vita (non mescolare e abbinare le allocazioni tra il codice C ++ e C) farà quello che vuoi. Dovrebbe essere perfettamente portatile tra i compilatori.

Il problema con i distruttori è reale, ma si applica ogni volta che il distruttore della classe base non è virtuale non solo per le strutture C. È qualcosa di cui devi essere consapevole ma che non preclude l'utilizzo di questo modello.

Funzionerà, e portabile MA NON puoi usare alcuna funzione virtuale (che include i distruttori).

Vorrei raccomandare che invece di farlo, Bar contiene un Foo.

class Bar
{
private:
   Foo  mFoo;
};

Non capisco perché non rendi semplicemente ret_foo un metodo membro. Il tuo modo attuale rende il tuo codice incredibilmente difficile da capire. Cosa c'è di così difficile nell'utilizzare una vera classe in primo luogo con una variabile membro e metodi get / set?

So che è possibile sottoclassare le strutture in C ++, ma il pericolo è che gli altri non siano in grado di capire ciò che hai codificato perché è così raro che qualcuno lo faccia davvero. Preferirei invece una soluzione solida e comune.

Probabilmente funzionerà ma non credo che sia garantito. Quella che segue è una citazione da ISO C ++ 10/5:

  

Un oggetto secondario di classe base potrebbe avere un layout (3.7) diverso dal layout di un oggetto più derivato dello stesso tipo.

È difficile vedere come nel "mondo reale" questo potrebbe effettivamente essere il caso.

MODIFICA:

La linea di fondo è che lo standard non ha limitato il numero di posizioni in cui un layout di oggetto secondario della classe base può essere diverso da un oggetto concreto con lo stesso tipo Base. Il risultato è che qualsiasi ipotesi che potresti avere, come POD-ness ecc., Non sono necessariamente vere per l'oggetto secondario della classe base.

Modifica

Un approccio alternativo e uno il cui comportamento è ben definito è quello di rendere "pippo" un membro di "bar" e fornire un operatore di conversione dove è necessario.

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_;
};
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top