当构建我的c++程序时,我得到了错误消息

未定义的引用'vtable…

这个问题的原因是什么?我该怎么解决呢?


碰巧,我得到以下代码的错误(类的问题是CGameModule.),我不能为我的生活理解问题是什么。起初,我认为这与忘记给虚拟函数赋予一个主体有关,但据我所知,一切都在这里。继承链有点长,但这里有相关的源代码。我不确定我还需要提供什么信息。

注意:这个错误似乎发生在构造函数中。

我的代码:

class CGameModule : public CDasherModule {
 public:
  CGameModule(Dasher::CEventHandler *pEventHandler, CSettingsStore *pSettingsStore, CDasherInterfaceBase *pInterface, ModuleID_t iID, const char *szName)
  : CDasherModule(pEventHandler, pSettingsStore, iID, 0, szName)
  { 
      g_pLogger->Log("Inside game module constructor");   
      m_pInterface = pInterface; 
  }

  virtual ~CGameModule() {};

  std::string GetTypedTarget();

  std::string GetUntypedTarget();

  bool DecorateView(CDasherView *pView) {
      //g_pLogger->Log("Decorating the view");
      return false;
  }

  void SetDasherModel(CDasherModel *pModel) { m_pModel = pModel; }


  virtual void HandleEvent(Dasher::CEvent *pEvent); 

 private:



  CDasherNode *pLastTypedNode;


  CDasherNode *pNextTargetNode;


  std::string m_sTargetString;


  size_t m_stCurrentStringPos;


  CDasherModel *m_pModel;


  CDasherInterfaceBase *m_pInterface;
};

继承自…

class CDasherModule;
typedef std::vector<CDasherModule*>::size_type ModuleID_t;

/// \ingroup Core
/// @{
class CDasherModule : public Dasher::CDasherComponent {
 public:
  CDasherModule(Dasher::CEventHandler * pEventHandler, CSettingsStore * pSettingsStore, ModuleID_t iID, int iType, const char *szName);

  virtual ModuleID_t GetID();
  virtual void SetID(ModuleID_t);
  virtual int GetType();
  virtual const char *GetName();

  virtual bool GetSettings(SModuleSettings **pSettings, int *iCount) {
    return false;
  };

 private:
  ModuleID_t m_iID;
  int m_iType;
  const char *m_szName;
};

哪个继承自....

namespace Dasher {
  class CEvent;
  class CEventHandler;
  class CDasherComponent;
};

/// \ingroup Core
/// @{
class Dasher::CDasherComponent {
 public:
  CDasherComponent(Dasher::CEventHandler* pEventHandler, CSettingsStore* pSettingsStore);
  virtual ~CDasherComponent();

  void InsertEvent(Dasher::CEvent * pEvent);
  virtual void HandleEvent(Dasher::CEvent * pEvent) {};

  bool GetBoolParameter(int iParameter) const;
  void SetBoolParameter(int iParameter, bool bValue) const;

  long GetLongParameter(int iParameter) const;
  void SetLongParameter(int iParameter, long lValue) const;

  std::string GetStringParameter(int iParameter) const;
  void        SetStringParameter(int iParameter, const std::string & sValue) const;

  ParameterType   GetParameterType(int iParameter) const;
  std::string     GetParameterName(int iParameter) const;

 protected:
  Dasher::CEventHandler *m_pEventHandler;
  CSettingsStore *m_pSettingsStore;
};
/// @}


#endif

当前回答

这是GCC中的一个错误特性。也就是说,g++编译器本身不能抱怨未定义的虚方法,因为它们可以在其他地方定义。但是-它不存储关于缺少哪些虚拟成员的信息;它只存储了一个und定义的虚表符号,链接器会抱怨。

相反,如果要列出缺少的成员,则链接器可以告诉您它们是什么。

关于这个问题,GCC有一个公开的bug: bug 42540。不幸的是,它已经13岁了。

其他回答

对于Qt用户使用CMakeList.txt

在你的CMakeLists.txt中添加这一行:

正如Chris Morler所解释的,如果你忘记moc一个头部,你会得到这个错误

所以,我已经找到了问题所在,这是一个糟糕的逻辑和不完全熟悉automake/autotools世界的组合。我正在向Makefile中添加正确的文件。am模板,但我不确定在我们的构建过程中哪一步实际上创建了makefile本身。所以,我用一个旧的makefile编译,它对我的新文件一无所知。

感谢您的回复以及GCC FAQ的链接。我一定会阅读它,以避免这个问题出现真正的原因。

如果您正在使用Qt,请尝试重新运行qmake。如果这个错误出现在小部件的类中,那么qmake可能没有注意到应该重新生成ui类虚表。这为我解决了问题。

这里有很多猜测,各种各样的答案。下面我将给出一个相当小的代码来重现这个错误,并解释为什么会出现这个错误。

重现此错误的代码相当少

IBase.hpp

#pragma once

class IBase {
    public:
        virtual void action() = 0;
};

Derived.hpp

#pragma once

#include "IBase.hpp"

class Derived : public IBase {
    public:
        Derived(int a);
        void action() override;
};

Derived.cpp

#include "Derived.hpp"
Derived::Derived(int a) { }
void Derived::action() {}

myclass.cpp

#include <memory>
#include "Derived.hpp"

class MyClass {

    public:
        MyClass(std::shared_ptr<Derived> newInstance) : instance(newInstance) {

        }

        void doSomething() {
            instance->action();
        }

    private:
        std::shared_ptr<Derived> instance;
};

int main(int argc, char** argv) {
    Derived myInstance(5);
    MyClass c(std::make_shared<Derived>(myInstance));
    c.doSomething();
    return 0;
}

你可以像这样使用GCC编译:

g++ -std=c++11 -o a.out myclass.cpp Derived.cpp

现在可以通过在hbase .hpp中删除= 0来重现错误。我得到这个错误:

~/.../catkin_ws$ g++ -std=c++11 -o /tmp/m.out /tmp/myclass.cpp /tmp/Derived.cpp
/tmp/cclLscB9.o: In function `IBase::IBase(IBase const&)':
myclass.cpp:(.text._ZN5IBaseC2ERKS_[_ZN5IBaseC5ERKS_]+0x13): undefined reference to `vtable for IBase'
/tmp/cc8Smvhm.o: In function `IBase::IBase()':
Derived.cpp:(.text._ZN5IBaseC2Ev[_ZN5IBaseC5Ev]+0xf): undefined reference to `vtable for IBase'
/tmp/cc8Smvhm.o:(.rodata._ZTI7Derived[_ZTI7Derived]+0x10): undefined reference to `typeinfo for IBase'
collect2: error: ld returned 1 exit status

解释

请注意,上面的代码不需要任何虚拟析构函数、构造函数或任何其他额外的文件才能成功编译(尽管您应该拥有它们)。

The way to understand this error is as follows: Linker is looking for constructor of IBase. This it will need it for the constructor of Derived. However as Derived overrides methods from IBase, it has vtable attached to it that will reference IBase. When linker says "undefined reference to vtable for IBase" it basically means that Derived has vtable reference to IBase but it can't find any compiled object code of IBase to look up to. So the bottom line is that class IBase has declarations without implementations. This means a method in IBase is declared as virtual but we forgot to mark it as pure virtual OR provide its definition.

分小费

如果所有这些都失败了,那么调试这个错误的一种方法是构建最小的程序,它可以编译,然后不断更改它,使它达到您想要的状态。在此期间,继续编译以查看它何时开始失败。

注意ROS和柳絮构建系统

如果你在ROS中使用catkin构建系统编译上述一组类,那么你将需要在CMakeLists.txt中执行以下命令:

add_executable(myclass src/myclass.cpp src/Derived.cpp)
add_dependencies(myclass theseus_myclass_cpp)
target_link_libraries(myclass ${catkin_LIBRARIES})

第一行基本上是说,我们想要创建一个名为myclass的可执行文件,构建它的代码可以在下面的文件中找到。其中一个文件应该有main()。请注意,您不必在CMakeLists.txt中的任何地方指定.hpp文件。此外,您不必将Derived.cpp指定为库。

GNU c++编译器必须决定将虚表放在哪里,以防对象的虚函数定义分布在多个编译单元中(例如,一些对象的虚函数定义在一个.cpp文件中,另一些在另一个.cpp文件中,等等)。

编译器选择将虚函数表放在与第一个声明的虚函数定义相同的位置。

现在,如果由于某种原因忘记为对象中声明的第一个虚函数提供定义(或者错误地忘记在链接阶段添加已编译的对象),您将得到这个错误。

作为一个副作用,请注意,只有对于这个特定的虚函数,你才不会得到传统的链接错误,比如你缺少函数foo。