2008-09-24 15 views
23

Y at-il un effet secondaire à faire:Est-il possible de sous-classer une structure C en C++ et d'utiliser des pointeurs vers la structure en code C?

Code C:

struct foo { 
     int k; 
}; 

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

C++ Code:

class bar : public foo { 

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

}; 

Il y a un extern "C" autour du code C++ et chaque code est à l'intérieur de sa propre compilation unité.

Est-ce portable sur les compilateurs?

+0

Je suppose que vous voulez dire (foo *) ceci, ou même mieux: static_cast (this) –

+0

Je suis curieux de savoir l'effet d'un extern "C" autour d'une classe avec une méthode ... raison pour laquelle vous voulez faire cela nous aiderait à offrir des informations ou des solutions alternatives. – paercebal

+0

Quand vous dites "portable sur les compilateurs", voulez-vous dire compiler le code C avec un compilateur, et le C++ avec un autre, et relier les deux ensemble? – Roddy

Répondre

26

Ceci est entièrement légal. En C++, les classes et les structures sont des concepts identiques, sauf que tous les membres de la structure sont publics par défaut. C'est la seule différence. Donc, demander si vous pouvez étendre une structure n'est pas différent de demander si vous pouvez étendre une classe.

Il y a une mise en garde ici. Il y a aucune garantie de cohérence de mise en page du compilateur au compilateur. Donc, si vous compilez votre code C avec un compilateur différent de votre code C++, vous risquez de rencontrer des problèmes liés à la mise en page des membres (padding en particulier). Cela peut même se produire lors de l'utilisation de compilateurs C et C++ du même fournisseur.

I l'avait fait avec gcc et g ++. J'ai travaillé sur un projet qui utilisait plusieurs grandes structures. Malheureusement, g ++ compressait les structures beaucoup plus facilement que gcc, ce qui provoquait des problèmes significatifs de partage d'objets entre les codes C et C++. Nous avons finalement dû régler manuellement l'emballage et l'insertion de rembourrage pour que le code C et C++ traite les structures de la même manière. Notez cependant que ce problème peut se produire indépendamment du sous-classement. En fait, nous ne sous-classions pas la structure C dans ce cas.

+1

En fait, il existe une autre différence entre les structures et les classes: la visibilité par défaut des classes de base est publique dans les structures, mais privée dans les classes. – Aconcagua

2

Wow, c'est le mal.

Est-ce portable sur les compilateurs?

Certainement pas. Considérez ce qui suit:

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

Pour que cela fonctionne, le destructeur de foo doit être virtuel ce qui n'est clairement pas le cas. Tant que vous n'utilisez pas new et tant que l'objet dérivé n'a pas de destructeur personnalisé, vous pouvez être chanceux.

/EDIT: D'un autre côté, si le code est seulement utilisé comme dans la question, l'héritage n'a aucun avantage sur la composition. Suivez simplement les conseils donnés par m_pGladiator.

+0

Mais ce n'est pas le code que j'ai l'intention d'écrire. La barre ne peut être convertie en foo pour les appels de fonction, et toutes les fonctions C qui fonctionnent sur foo sont const. (comme dans l'exemple.) –

+0

Oui, dans ce cas, le code est un peu OK. Il ne fait que demander une mauvaise utilisation. ;-) –

+0

Il semble que l'interface foo * est utilisée par C seulement. Le code C ne serait pas en mesure de le supprimer de toute façon: [1] ce n'est pas le propriétaire et [2] ce n'est pas C++. – MSalters

9

Je ne recommande certainement pas d'utiliser un tel sous-classement bizarre. Il vaudrait mieux changer de design pour utiliser la composition au lieu de l'héritage. Juste un membre

foo * m_pfoo;

dans la classe de barre et il fera le même travail.

Autre chose que vous pouvez faire est de faire un FooWrapper de classe supplémentaire, contenant la structure en elle-même avec la méthode getter correspondante. Ensuite, vous pouvez sous-classer le wrapper. De cette façon, le problème avec le destructeur virtuel est parti.

+0

c'est la meilleure réponse. Contrairement à la réponse acceptée, quelques options ont permis d'éviter les problèmes avec vtables et la structure de la structure. – Alex

1

Je ne pense pas que ce soit nécessairement un problème. Le comportement est bien défini, et tant que vous êtes prudent avec les problèmes de durée de vie (ne pas mélanger et assortir les allocations entre le code C++ et C) fera ce que vous voulez. Il devrait être parfaitement portable à travers les compilateurs.

Le problème avec les destructeurs est réel, mais s'applique chaque fois que le destructeur de classe de base n'est pas virtuel non seulement pour les structures C. C'est quelque chose dont vous devez être conscient mais qui n'empêche pas d'utiliser ce modèle.

+0

Le comportement n'est pas bien défini - au moins dans ma lecture de la norme. Avez-vous une référence au texte qui dit que c'est légal? –

+0

Aucune référence ... mais je ne crois pas complètement la citation que vous avez utilisée ci-dessous. Cela n'empêcherait-il pas de down-casting utile, je ne vois pas en quoi il est spécifique au cas de la classe de base étant un struct struct –

+0

Malheureusement, je suis incapable de trouver une référence normative. Mais il m'a été signalé par un membre du comité ISO C++ qu'un objet est un POD seulement s'il s'agit d'une instance concrète d'un type POD. c'est à dire. il n'y a pas de sous-objet de base POD. Je vais ajouter plus de détails à ma réponse ci-dessous. –

1

Cela fonctionnera, et portativement MAIS vous ne pouvez pas utiliser de fonctions virtuelles (ce qui inclut les destructeurs).

Je recommanderais que, au lieu de faire cela, Bar contient un Foo.

class Bar 
{ 
private: 
    Foo mFoo; 
}; 
0

Je ne comprends pas pourquoi vous ne faites pas simplement ret_foo une méthode membre. Votre façon actuelle rend votre code terriblement difficile à comprendre. Qu'est-ce qui rend si difficile l'utilisation d'une classe réelle en premier lieu avec une variable membre et des méthodes get/set?

Je sais qu'il est possible de sous-classer des structures en C++, mais le danger est que d'autres ne seront pas en mesure de comprendre ce que vous avez codé parce qu'il est si rare que quelqu'un le fasse réellement. J'irais plutôt pour une solution robuste et commune.

+0

Tout simplement parce que ret_foo est écrit en C pur, où je n'ai pas de méthodes membres. De plus, je n'ai pas le code pour ret_foo, c'est une API fournie pour moi. –

+0

Ne pas sous-classer des choses sur lesquelles vous n'avez aucun contrôle. Faites-en une variable membre et gérez-la ici. Enveloppez-le dans un objet si utile. La composition est bien meilleure ici que la dérivation. – Thorsten79

0

Cela fonctionnera probablement mais je ne crois pas qu'il soit garanti. Ce qui suit est une citation d'ISO C++ 10/5:

Un sous-objet de classe de base peut avoir une disposition (3.7) différente de la disposition d'un objet le plus dérivé du même type.

Il est difficile de voir comment dans le "monde réel" cela pourrait être le cas.

EDIT:

L'essentiel est que la norme n'a pas limité le nombre d'endroits où une disposition de classe de base peut être sous-objet différent d'un objet concret avec ce même type de base. Le résultat est que les hypothèses que vous pouvez avoir, telles que POD-ness etc. ne sont pas nécessairement vraies pour le sous-objet de la classe de base.

EDIT:

Une autre approche et un dont le comportement est bien défini est de faire « foo » un membre de la « barre » et de fournir un opérateur de conversion où il est nécessaire.

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_; 
}; 
+0

Je pense que cela serait dû à l'héritage virtuel. – bk1e

+0

Le problème est, je ne crois pas que la norme limite réellement l'endroit où cela peut arriver. –

+0

Richard, cette citation s'applique indépendamment du fait que vous utilisiez des structures ou des classes, ou un mélange funky. Tout ce qui est dit, c'est que le compilateur peut changer la disposition sous-jacente de l'objet comme il l'entend. Cela ne casse rien. –

3

« Ne jamais dériver des classes concrètes. » - Sutter

« Faire des classes non-feuille abstraite. » - Meyers

Il est tout simplement faux de sous-classe des classes non-interface. Vous devriez refactoriser vos bibliothèques. Techniquement, vous pouvez faire ce que vous voulez, tant que vous n'invoquez pas de comportement non défini par, e. g., en supprimant un pointeur vers la classe dérivée par un pointeur vers son sous-objet de classe de base. Vous n'avez même pas besoin de extern "C" pour le code C++. Oui, c'est portable. Mais c'est un mauvais design.

+4

Je ne suis pas du tout d'accord avec les déclarations définitives de Meyer et Sutter. Il y a beaucoup de choses que vous pouvez faire en héritant des classes concrètes, surtout si vous ajoutez seulement des méthodes dans les classes dérivées, pas des membres de données. De cette façon, vous n'avez pas le problème de trancher. – QBziZ

+2

Je pense que vous manquez le point. Je suppose que l'OP a des modules C existants et souhaite "envelopper" certaines structures dans les classes, tout en offrant une interface C pour les systèmes existants. S'il est coincé avec C pour les structures, Sutter et Meyer ne sont d'aucune aide. – Roddy

3

Ceci est parfaitement légal, bien qu'il puisse être déroutant pour d'autres programmeurs.

Vous pouvez utiliser l'héritage pour étendre des structures C avec des méthodes et des constructeurs.

Exemple:

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

pourraient être struct L'extension « plus » mal, mais n'est pas quelque chose que vous êtes interdit de le faire.

+0

Je ne suis pas si sûr de ça. Je crois (au moins dans la version actuelle de la norme) que la mise en page d'une classe en tant que sous-objet de classe de base peut être différente que si vous avez une instance de cette classe. (Voir la note sous ISO C++ 10/5) –

+1

Si la classe dérivée n'ajoute aucune donnée et n'ajoute aucune méthode virtuelle, alors rien de mauvais ne devrait arriver. Maintenant, c'est peut-être mal dans la conception, mais si le besoin est d'ajouter un constructeur par défaut et quelques fonctions d'aide, alors ... Eh bien ... Ce n'est pas du design. C'est juste corriger une caractéristique manquante, rien de plus. – paercebal

2

Ceci est parfaitement légal, et vous pouvez le voir en pratique avec les classes MFC CRect et CPoint. CPoint dérive de POINT (défini dans windef.h), et CRect dérive de RECT. Vous décorez simplement un objet avec des fonctions membres. Tant que vous ne prolongez pas l'objet avec plus de données, tout va bien. En fait, si vous avez une structure C complexe difficile à initialiser, l'étendre à une classe qui contient un constructeur par défaut est un moyen facile de résoudre ce problème.

Même si vous faites ceci:

foo *pFoo = new bar; 
delete pFoo; 

alors vous êtes très bien, puisque votre constructeur et destructor sont insignifiants, et que vous avez attribué aucune mémoire supplémentaire.

Vous ne devez pas non plus envelopper votre objet C++ avec 'extern' C "', puisque vous ne passez pas réellement un C++ tapez aux fonctions C.

+0

Vous avez tort. La suppression d'un pointeur sur une classe dérivée par un pointeur sur une classe de base est un comportement indéfini. Période. –