undefined reference to vtable...



  • Guten Abend liebe Community,

    Ich bin gerade auf ein Problem gestoßen und habe Probleme es nachzuvollziehen. Ich habe ein interface IPosixReader

    namespace ls::std::core::interface_type
    {
      class IPosixReader
      {
        public:
    
          // IPosixReader() = default; stand hier auch schon
          virtual ~IPosixReader() = default;
    
          virtual size_t read(int _unixFileDescriptor, void* _buffer, size_t _size) = 0;
      };
    }
    

    Eine Klasse, die es implementiert (PosixReader):

    namespace ls::std::core::api
    {
      class PosixReader : public ls::std::core::interface_type::IPosixReader
      {
        public:
    
          PosixReader() = default;
          ~PosixReader() override = default; // oder "virtual ~PosixReader() = default" 
    
          size_t read(int _unixFileDescriptor, void* _buffer, size_t _size) override;
      };
    }
    

    Und eine Klasse Socket, in der ich eine Referenz erzeuge:

    // ..
    
    void ls::std::network::Socket::_setPosixReaderApi()
    {
      if (this->parameter.posixReader == nullptr)
      {
        ::std::shared_ptr<ls::std::core::interface_type::IPosixReader> posixReader = ::std::make_shared<ls::std::core::api::PosixReader>();
    
        // ...
      }
    }
    

    Das erzeugt folgenden Fehler beim Kompilieren:

    /usr/bin/ld: libls_std_network.a(Socket.cpp.o): in function `ls::std::core::api::PosixReader::PosixReader()':
    Socket.cpp:(.text._ZN2ls3std4core3api11PosixReaderC2Ev[_ZN2ls3std4core3api11PosixReaderC5Ev]+0x19): undefined reference to `vtable for ls::std::core::api::PosixReader'
    

    Was ich nicht verstehe ist,

    • dass ich die virtuellen Methoden implementiere oder override und ich diesen Fehler kriege
    • wenn ich das Erzeugen der Referenz innerhalb der Socket Klasse auskommentiere (hier in Zeile 7 des Code Schnipsels), kompiliert es
    • wenn ich ein frisches minimales Projekt baue kompiliert es ebenfalls (mit gleichen namespaces)
    • wenn ich, und hier wird es merkwürdig, Tests für PosixReader implementiere, das Kompilieren des Projektes und das Ausführen aller Tests innerhalb meines Projektes erfolgreich ist (sprich einfach alles plötzlich funktioniert)

    Zum letzten Punkt:
    Ich habe mit diesem commit temporäre Tests für PosixReader implementiert (und auch PosixSocket - gleiches Problem übrigens) - wenn ich im CMake Projekt die Tests auskommentiere, bekomme ich wieder den Compilerfehler, wegen der besagten Zeile in der Socket Klasse, in der ich eine Referenz erzeuge. Wenn ich allerdings die Tests im Projekt habe, kompiliert es (auch die Socket Klasse) und die Tests werden ausgeführt.

    Es ist dabei übrigens egal, ob ich dafür meine IDE nutze, oder ein CLI mit frischem CMake Projekt. Ich habe auch das CMake Projekt innerhalb der IDE mal komplett weggeräumt - funktioniert nicht.
    Was passiert hier?

    OS: Linux Mint 20.3
    Compiler: GCC 12.2
    IDE: CLion 2022.3.1
    Branch: socket
    Files Of Interests: IPosixReader.hpp , PosixReader.hpp , PosixReader.cpp , Socket.hpp , Socket.cpp , PosixReaderTest.cpp

    Jede Hilfe ist hier echt willkommen!



  • @PadMad Wenn ich mir diesen FAQ-Eintrag anschaue:

    The ISO C++ Standard specifies that all virtual methods of a class that are not pure-virtual must be defined, but does not require any diagnostic for violations of this rule [class.virtual]/8. Based on this assumption, GCC will only emit the implicitly defined constructors, the assignment operator, the destructor and the virtual table of a class in the translation unit that defines its first such non-inline method.

    ... dann glaube ich, dass virtual ~IPosixReader() = default; dem nicht genügt, da dieses äquivalent zu inline virtual ~IPosixReader() {}; ist und im FAQ die Rede von der TU mit der ersten nicht-inline-Definition ist.

    Du solltest mal folgendes versuchen:

    IPosixReader.hpp:

    namespace ls::std::core::interface_type
    {
      class IPosixReader
      {
        public:
          virtual ~IPosixReader();
          ...
      };
    }
    

    IPosixReader.cpp:

    namespace ls::std::core::interface_type
    {
      IPosixReader::~IPosixReader()
      {
      }
    }
    

    ... und schauen, ob das eventuell funktioniert.

    Ich denke, dass du dir hier die Symbole während des Linkens über libls_std_network.a reinholst, spielt auch eine Rolle. In einem kurzen Test mit nur einer Quellcode-Datei main.cpp konnte ich das Problem jedenfalls nicht reproduzieren.



  • Hey,

    Vielen Dank. Tatsächlich war das Auslagern der Interface Definition in eine Source Datei hilfreich.
    In meinem Fall hat das dann zwar zu weiteren Linker Fehlern geführt, allerdings hatten die nichts mehr mit vtable zu tun und die konnte ich dann auch ohne Probleme lösen.



  • @PadMad sagte in undefined reference to vtable...:

    Hey,

    Vielen Dank. Tatsächlich war das Auslagern der Interface Definition in eine Source Datei hilfreich.
    In meinem Fall hat das dann zwar zu weiteren Linker Fehlern geführt, allerdings hatten die nichts mehr mit vtable zu tun und die konnte ich dann auch ohne Probleme lösen.

    Gut zu wissen, das werd ich mir auch mal merken. Ich finde das Verhalten von GCC hier auch überraschend - auch ich hätte das wahrscheinlich erstmal mit = default gelöst.

    Problematisch finde ich an der Art, wie GCC das hier handhabt zwei Dinge:

    1. Eine "Header Only"-Implementierung scheint so nicht möglich zu sein, es sei denn es gibt noch einen irgendeinen anderen Trick.
    2. Wegoptimieren des virtuellen Destruktors wird erschwert, wenn man z.B. keine LTO verwendet, da der Compiler nicht wirklich weiss, dass der in einer anderen TU definierte Destruktor leer ist und keine Nebeneffekte hat. Das müsste ich mir nochmal genauer ansehen, was hier genau passiert - es wäre jedenfalls ungünstig, wenn jedes mal eine leere Funktion mit call/ret aufgerufen würde.

Anmelden zum Antworten