Mehrfachdefinitionsfehler einschließlich C++-Header-Datei mit Inline-Code aus mehreren Quellen

Lesezeit: 5 Minuten

Benutzer-Avatar
Paul Tedesco

Ich habe eine C++-Header-Datei, die eine Klasse enthält. Ich möchte diese Klasse in mehreren Projekten verwenden, aber ich möchte keine separate Bibliothek dafür erstellen, also füge ich sowohl Methodendeklarationen als auch Definitionen in die Header-Datei ein:

// example.h
#ifndef EXAMPLE_H_
#define EXAMPLE_H_
namespace test_ns{

class TestClass{
public:
    void testMethod();
};

void TestClass::testMethod(){
    // some code here...
}

} // end namespace test_ns
#endif

Wenn ich diesen Header aus mehr als einer cpp-Datei in dasselbe Projekt einfüge, erhalte ich die Fehlermeldung „multiple definition of test_ns::TestClass::testMethod()“, während dies nicht passiert, wenn ich die Methodendefinition in den Klassenkörper einfüge:

// example.h
#ifndef EXAMPLE_H_
#define EXAMPLE_H_
namespace test_ns{

class TestClass{
public:
    void testMethod(){
        // some code here...
    }
};

} // end namespace test_ns
#endif

Da die Klasse innerhalb eines Namensraums definiert ist, sollten die beiden Formen nicht gleichwertig sein? Warum gilt die Methode im ersten Fall als doppelt definiert?

Diese sind nicht gleichwertig. Das zweite gegebene Beispiel hat einen impliziten ‘Inline’-Modifikator für die Methode, und daher gleicht der Compiler mehrere Definitionen selbst ab (höchstwahrscheinlich mit interner Verknüpfung der Methode, wenn sie nicht inlinefähig ist).

Das erste Beispiel ist nicht inline. Wenn dieser Header also in mehreren Übersetzungseinheiten enthalten ist, treten mehrere Definitionen und Linkerfehler auf.

Außerdem sollten Kopfzeilen wirklich immer geschützt werden, um mehrere Definitionsfehler in derselben Übersetzungseinheit zu vermeiden. Das sollte Ihren Header konvertieren in:

#ifndef EXAMPLE_H
#define EXAMPLE_H

//define your class here

#endif

  • Danke für den Hinweis … Ich hatte die Include-Wächter im Beispiel vergessen (aber nicht im eigentlichen Code).

    – Paolo Tedesco

    17. Oktober 2008 um 13:09 Uhr

Benutzer-Avatar
QBziZ

Innerhalb des Klassenkörpers wird der Compiler als inline betrachtet. Wenn Sie außerhalb des Körpers implementieren, aber immer noch im Header, müssen Sie die Methode explizit als “inline” markieren.

namespace test_ns{

class TestClass{
public:
    inline void testMethod();
};

void TestClass::testMethod(){
    // some code here...
}

} // end namespace test_ns

Bearbeiten

Für mich selbst hilft es oft, solche Kompilierungsprobleme zu lösen, indem ich erkenne, dass der Compiler so etwas wie eine Header-Datei nicht sieht. Header-Dateien werden vorverarbeitet und der Compiler sieht nur eine riesige Datei, die jede Zeile aus jeder (rekursiv) eingebundenen Datei enthält. Normalerweise ist der Ausgangspunkt für diese rekursiven Includes eine cpp-Quelldatei, die kompiliert wird. In unserem Unternehmen kann selbst eine bescheiden aussehende cpp-Datei dem Compiler als 300000-Zeilen-Monster präsentiert werden.

Wenn also eine Methode, die nicht inline deklariert ist, in einer Header-Datei implementiert wird, könnte der Compiler am Ende dutzende Male void TestClass::testMethod() {…} in der vorverarbeiteten Datei sehen. Jetzt können Sie sehen, dass dies keinen Sinn macht, der gleiche Effekt wie beim mehrmaligen Kopieren/Einfügen in eine Quelldatei. Und selbst wenn es Ihnen gelingen würde, es nur einmal in jeder Kompilierungseinheit zu haben, würde der Linker durch irgendeine Form der bedingten Kompilierung (zB Verwendung von Einschlussklammern) das Symbol dieser Methode immer noch in mehreren kompilierten Einheiten ( Objektdateien ) finden.

Fügen Sie keine Funktions-/Methodendefinition in eine Header-Datei ein, es sei denn, sie sind eingebettet (indem Sie sie direkt in einer Klassendeklaration definieren oder explizit durch das Schlüsselwort inline angeben).

Header-Dateien sind (meistens) für die Deklaration (was auch immer Sie deklarieren müssen). Erlaubt sind Definitionen für Konstanten und eingebettete Funktionen/Methoden (und auch Templates).

Tatsächlich ist es möglich, Definitionen in einer einzigen Header-Datei (ohne separate .c/.cpp-Datei) zu haben und sie dennoch aus mehreren Quelldateien verwenden zu können.

Bedenken Sie foobar.h Header:

#ifndef FOOBAR_H
#define FOOBAR_H

/* write declarations normally */
void foo();
void bar();

/* use conditional compilation to disable definitions when necessary */
#ifndef ONLY_DECLARATIONS
void foo() {
   /* your code goes here */
}
void bar() {
   /* your code goes here */
}
#endif /* ONLY_DECLARATIONS */
#endif /* FOOBAR_H */

Wenn Sie diesen Header nur in einer Quelldatei verwenden, schließen Sie ihn ein und verwenden Sie ihn normal. Wie in main.c:

#include "foobar.h"

int main(int argc, char *argv[]) {
    foo();
}

Wenn es andere Quelldateien in Ihrem Projekt gibt, die erfordern foobar.hdann #define ONLY_DECLARATIONS Makro, bevor Sie es einfügen. In use_bar.c du darfst schreiben:

#define ONLY_DECLARATIONS
#include "foobar.h"

void use_bar() {
    bar();
}

Nach der Kompilierung können use_bar.o und main.o ohne Fehler miteinander verknüpft werden, da nur einer von ihnen (main.o) die Implementierung von foo() und bar() haben wird.

Das ist etwas nicht idiomatisch, aber es erlaubt, Definitionen und Deklarationen in einer Datei zusammenzuhalten. Ich habe das Gefühl, dass es der Ersatz eines armen Mannes für wahr ist Module.

Ihr erstes Code-Snippet verstößt gegen die “One Definition Rule” von C++ – siehe hier für einen Link zu einem Wikipedia-Artikel, der ODR beschreibt. Sie verstoßen tatsächlich gegen Punkt 2, denn jedes Mal, wenn der Compiler die Header-Datei in eine Quelldatei einfügt, laufen Sie Gefahr, dass der Compiler eine global sichtbare Definition von generiert test_ns::TestClass::testMethod(). Und zu dem Zeitpunkt, an dem Sie den Code verknüpfen können, hat der Linker natürlich Kätzchen, da er dasselbe Symbol in mehreren Objektdateien findet.

Das zweite Snippet funktioniert, weil Sie die Definition der Funktion inliniert haben, was bedeutet, dass selbst wenn der Compiler keinen Inline-Code für die Funktion generiert (sagen wir, Sie haben Inlining deaktiviert oder der Compiler entscheidet, dass die Funktion es auch ist). big to inline), ist der für die Funktionsdefinition generierte Code nur in der Übersetzungseinheit sichtbar, als ob Sie ihn in einen anonymen Namensraum gesteckt hätten. Daher erhalten Sie mehrere Kopien der Funktion im generierten Objektcode, die der Linker möglicherweise wegoptimiert oder nicht, je nachdem, wie intelligent er ist.

Einen ähnlichen Effekt könnten Sie in Ihrem ersten Codeschnipsel durch Präfixierung erzielen TestClass::testMethod() mit inline.

Benutzer-Avatar
Mahendra

//Baseclass.h  or  .cpp

#ifndef CDerivedclass
#include "Derivedclass.h"
#endif

or
//COthercls.h    or .cpp

#ifndef CCommonheadercls
#include "Commonheadercls.h"
#endif

I think this suffice all instances.

1012980cookie-checkMehrfachdefinitionsfehler einschließlich C++-Header-Datei mit Inline-Code aus mehreren Quellen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy