2008-09-24 11 views
23

Gibt es eine Nebenwirkung dies tun:Ist es möglich, eine C-Struktur in C++ ableiten und Zeiger auf die Struktur in C-Code verwenden?

C-Code:

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

}; 

Es gibt eine extern "C" um den C++ Code und jeder Code ist innerhalb seiner eigenen Compilation Einheit.

Ist dies über Compiler übertragbar?

+0

Ich vermute, du meinst (foo *) dies oder noch besser: static_cast (dies) –

+0

Ich bin neugierig auf die Wirkung eines externen "C" um eine Klasse mit einer Methode ... Dennoch, die Wenn Sie dies tun möchten, können wir Ihnen Informationen oder alternative Lösungen anbieten. – paercebal

+0

Wenn Sie "portable über Compiler" sagen, meinen Sie, den C-Code mit einem Compiler und das C++ mit einem anderen Compiler zu kompilieren? – Roddy

Antwort

26

Dies ist völlig legal. In C++ sind Klassen und Strukturen identische Konzepte, mit der Ausnahme, dass alle Strukturelemente standardmäßig öffentlich sind. Das ist der einzige Unterschied. Die Frage, ob Sie eine Struktur erweitern können, unterscheidet sich nicht von der Frage, ob Sie eine Klasse erweitern können.

Hier gibt es einen Vorbehalt. Es gibt keine Garantie der Layoutkonsistenz von Compiler zu Compiler. Wenn Sie also Ihren C-Code mit einem anderen Compiler als Ihrem C++ - Code kompilieren, können Probleme mit dem Element-Layout (insbesondere dem Padding) auftreten. Dies kann sogar auftreten, wenn C- und C++ - Compiler vom selben Hersteller verwendet werden.

I haben hatte dies mit gcc und g ++ passieren. Ich arbeitete an einem Projekt, das mehrere große Strukturen verwendete. Leider packte g ++ die Strukturen wesentlich looser als gcc, was zu erheblichen Problemen bei der Freigabe von Objekten zwischen C- und C++ - Code führte. Wir mussten schließlich manuell packen und einfügen einfügen, damit der C- und C++ - Code die Strukturen gleich behandelt. Beachten Sie jedoch, dass dieses Problem unabhängig von Unterklassen auftreten kann. Tatsächlich haben wir die C-Struktur in diesem Fall nicht abgeleitet.

+1

Tatsächlich gibt es einen weiteren Unterschied zwischen Strukturen und Klassen: Die Standardsichtbarkeit von Basisklassen ist in Strukturen öffentlich, aber in Klassen privat. – Aconcagua

2

Wow, das ist böse.

Ist dies über Compiler übertragbar?

Auf jeden Fall nicht. Berücksichtigen Sie Folgendes:

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

Damit das funktioniert, muss der Destruktor von foo virtuell sein, was er eindeutig nicht ist. Solange Sie nicht new verwenden und solange das abgeleitete Objekt nicht über benutzerdefinierte Destruktoren verfügt, können Sie Glück haben.

/EDIT: Auf der anderen Seite, wenn der Code nur wie in der Frage verwendet wird, hat die Vererbung keinen Vorteil gegenüber der Zusammensetzung. Befolgen Sie einfach den Rat von m_pGladiator.

+0

Aber das ist nicht der Code, den ich schreiben möchte. bar kann nur für Funktionsaufrufe übergeben werden, und alle C-Funktionen, die auf foo arbeiten, sind const. (Genau wie im Beispiel.) –

+0

Ja, in diesem Fall ist der Code ein bisschen in Ordnung. Es bittet nur um Missbrauch. ;-) –

+0

Es scheint, dass das foo * Interface nur von C benutzt wird. Der C-Code könnte es sowieso nicht löschen: [1] es ist nicht der Besitzer und [2] es ist nicht C++. – MSalters

9

Ich empfehle sicherlich nicht solche seltsamen Unterklassen zu verwenden. Es wäre besser, das Design so zu ändern, dass die Komposition statt der Vererbung verwendet wird. Nur ein Mitglied machen

foo * m_pfoo;

in der Bar-Klasse und es wird die gleiche Arbeit machen.

Andere Sache, die Sie tun können, ist eine weitere Klasse FooWrapper zu machen, die die Struktur in sich mit der entsprechenden Getter-Methode enthält. Dann können Sie den Wrapper unterklassifizieren. Auf diese Weise ist das Problem mit dem virtuellen Destruktor verschwunden.

+0

das ist die beste Antwort. Im Gegensatz zur angenommenen Antwort helfen einige Optionen, Probleme mit vtables und dem Strukturlayout zu vermeiden. – Alex

1

Ich glaube nicht, dass es ein Problem ist. Das Verhalten ist gut definiert, und solange Sie mit den Problemen der Lebensdauer vorsichtig umgehen (mischen Sie nicht und stimmen Sie die Zuweisungen zwischen C++ - und C-Code nicht überein), werden Sie tun, was Sie wollen. Es sollte über Compiler hinweg portabel sein.

Das Problem mit Destruktoren ist real, gilt aber immer dann, wenn der Destruktor der Basisklasse nicht virtuell ist, nicht nur für C-Strukturen. Es ist etwas, was Sie beachten müssen, schließt aber nicht aus, dieses Muster zu verwenden.

+0

Das Verhalten ist nicht gut definiert - zumindest in meiner Lesung der Std. Haben Sie einen Hinweis auf den Text, der besagt, dass es legal ist? –

+0

Kein Hinweis ... aber ich bin nicht vollständig das Zitat, das Sie unten verwendeten. Wäre das nicht sinnvolles Downcasting, sehe ich nicht, wie spezifisch es für den Fall ist, dass die Basisklasse eine C-Struktur ist. –

+0

Leider kann ich keine normative Referenz finden. Aber es wurde mir von einem ISO C++ Komitee-Mitglied darauf hingewiesen, dass ein Objekt nur dann ein POD ist, wenn es sich um eine konkrete Instanz eines POD-Typs handelt. dh. Es gibt kein POD-Basisunterobjekt. Ich füge mehr Details zu meiner Antwort unten hinzu. –

1

Es wird funktionieren, und portabel ABER Sie können keine virtuellen Funktionen (einschließlich Destruktoren) verwenden.

Ich würde empfehlen, dass Sie anstatt dies zu tun haben Bar enthalten ein Foo.

class Bar 
{ 
private: 
    Foo mFoo; 
}; 
0

Ich verstehe nicht, warum Sie nicht einfach re_foo eine Member-Methode machen. Ihr aktueller Weg macht Ihren Code schwer verständlich. Was ist so schwierig daran, eine echte Klasse mit einer Member-Variablen und Methoden zum Abrufen/Setzen zu verwenden?

Ich weiß, dass es möglich ist, Structs in C++ zu subklassen, aber die Gefahr ist, dass andere nicht in der Lage sind, zu verstehen, was du codiert hast, weil es so selten ist, dass jemand es tatsächlich tut. Ich würde stattdessen für eine robuste und gemeinsame Lösung gehen.

+0

Ganz einfach, weil ret_foo in reinem C geschrieben ist, wo ich keine Member-Methoden habe. Außerdem habe ich nicht den Code für ret_foo, es ist eine API für mich. –

+0

Unterlassen Sie keine Sachen, über die Sie keine Kontrolle haben. Machen Sie es zu einer Membervariable und behandeln Sie sie dort. Wickeln Sie es in ein Objekt ein, wenn es nützlich ist. Die Zusammensetzung ist hier viel besser als die Ableitung. – Thorsten79

0

Es wird wahrscheinlich funktionieren, aber ich glaube nicht, dass es garantiert ist. Folgendes ist ein Zitat aus ISO C++ 10/5:

Ein Basisklassenunterobjekt kann ein Layout (3.7) haben, das sich vom Layout eines am meisten abgeleiteten Objekts desselben Typs unterscheidet.

Es ist schwer zu sehen, wie dies in der "realen Welt" tatsächlich der Fall sein könnte.

EDIT:

Unterm Strich ist, dass der Standard die Anzahl der Plätze begrenzt ist, wo eine Basisklasse subobject Layout kann mit dem gleichen Basistyp aus einem konkreten Objekt unterschiedlich sein. Das Ergebnis ist, dass alle Annahmen, die Sie möglicherweise haben, wie POD-Ness usw. nicht unbedingt für das Unterobjekt der Basisklasse gelten.

EDIT:

Ein alternativer Ansatz, und einer, dessen Verhalten definiert ist gut ist ‚foo‘ Mitglied von ‚bar‘ zu machen und einen Umwandlungsoperator zur Verfügung zu stellen, wo es notwendig ist.

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

Ich vermute, dass dies auf virtuelle Vererbung zurückzuführen wäre. – bk1e

+0

Das Problem ist, ich glaube nicht, dass der Standard tatsächlich den Ort begrenzt, an dem es passieren kann. –

+0

Richard, das Zitat gilt unabhängig davon, ob Sie Strukturen oder Klassen oder eine funky Mischung verwenden. Das heißt, dass der Compiler das zugrunde liegende Layout des Objekts ändern kann, wie es ihm passt. Das macht nichts kaputt. –

3

„ableiten nie von konkreten Klassen.“ - Sutter

„Machen Sie nicht-Blatt Klassen abstrakt.“ - Meyers

Es ist einfach falsch nicht-Interface-Klassen, Unterklasse. Sie sollten Ihre Bibliotheken umgestalten.

Technisch können Sie tun, was Sie wollen, solange Sie kein undefiniertes Verhalten durch z. B. einen Zeiger auf die abgeleitete Klasse durch einen Zeiger auf sein Basisklassenunterobjekt löschen. Sie brauchen nicht einmal extern "C" für den C++ - Code. Ja, es ist tragbar. Aber es ist schlechtes Design.

+4

Ich stimme überhaupt nicht mit solchen endgültigen Aussagen von Meyer und Sutter überein. Es gibt eine Menge, die Sie tun können, indem Sie von konkreten Klassen erben, besonders wenn Sie nur Methoden in den abgeleiteten Klassen hinzufügen, nicht Datenelemente. Auf diese Weise haben Sie nicht das Problem des Schneidens. – QBziZ

+2

Ich denke, du verpasst den Punkt. Meine Vermutung ist, dass das OP existierende C-Module hat und einige der Strukturen in Klassen "umhüllen" will, während es immer noch eine C-Schnittstelle für Altsysteme bietet. Wenn er für die Strukturen an C festhält, sind Sutter und Meyer keine Hilfe. – Roddy

3

Dies ist vollkommen legal, obwohl es für andere Programmierer verwirrend sein könnte.

Sie können Vererbung verwenden, um C-Strukturen mit Methoden und Konstruktoren zu erweitern.

Probe:

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

Erweiterung structs könnte „mehr“ böse sein, aber es ist nicht etwas, was Sie zu tun, sind verboten.

+0

Ich bin mir da nicht so sicher. Ich glaube (zumindest in der aktuellen Version des Standards), dass das Layout einer Klasse als Basisklassenunterobjekt anders sein kann, als wenn Sie eine Instanz dieser Klasse haben. (Siehe Hinweis unter ISO C++ 10/5) –

+1

Wenn die abgeleitete Klasse keine Daten hinzufügt und keine virtuellen Methoden hinzufügt, sollte nichts Schlimmes passieren. Nun, das ist vielleicht schlecht im Design, aber wenn es notwendig ist, einen Standardkonstruktor und einige Hilfsfunktionen hinzuzufügen, dann ... Nun ... Das ist kein Design. Es korrigiert nur eine fehlende Funktion, nichts mehr. – paercebal

2

Dies ist vollkommen legal, und Sie können es in der Praxis mit den Klassen MFC CRect und CPoint sehen. CPoint stammt von POINT (in windef.h definiert) und CRect von RECT. Sie dekorieren einfach ein Objekt mit Elementfunktionen. Solange Sie das Objekt nicht mit mehr Daten erweitern, geht es Ihnen gut. In der Tat, wenn Sie eine komplexe C-Struktur haben, bei der die Standardinitialisierung ein Problem darstellt, ist die Erweiterung mit einer Klasse, die einen Standardkonstruktor enthält, eine einfache Möglichkeit, um mit diesem Problem umzugehen.

Auch wenn Sie dies tun:

foo *pFoo = new bar; 
delete pFoo; 

dann bist du in Ordnung, da Ihr und Destruktor trivial sind, und Sie haben keinen zusätzlichen Speicher zugewiesen.

Sie müssen auch Ihr C++ Objekt nicht mit 'extern "C' 'umhüllen, da Sie nicht tatsächlich einen C++ Typ an die C-Funktionen übergeben.

+0

Sie liegen falsch. Das Löschen eines Zeigers auf eine abgeleitete Klasse durch einen Zeiger auf eine Basisklasse ist ein nicht definiertes Verhalten. Zeitraum. –