当构建我的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

当前回答

我刚刚遇到了另一个导致这个错误的原因,你可以检查一下。

基类将纯虚函数定义为:

virtual int foo(int x = 0);

子类有

int foo(int x) override;

问题是“=0”应该在括号外的拼写错误:

virtual int foo(int x) = 0;

所以,如果你向下滚动这么远,你可能没有找到答案,这是另一个需要检查的东西。

其他回答

对vtable的未定义引用也可能由于以下情况而发生。试试这个:

A类包含:

virtual void functionA(parameters)=0; 
virtual void functionB(parameters);

B类包含:

上述函数a的定义。 上述函数b的定义。

类C包含:现在你正在编写一个类C,你将从类a派生它。

现在如果你尝试编译,你会得到未定义的引用vtable类C作为错误。

原因:

函数a被定义为纯虚函数,其定义在类B中提供。 函数B被定义为虚(不是纯虚),所以它试图在类A中找到它的定义,但你在类B中提供了它的定义。

解决方案:

使函数B为纯虚函数(如果你有这样的要求) 虚空函数b(参数)=0; (这是经过测试的) 在类A中为函数b提供定义,保持它为虚的。 (希望它能起作用,因为我没有尝试过这个)

如果所有这些都失败了,那就寻找副本。我被构造函数和析构函数的显式初始引用误导了,直到我在另一篇文章中读到引用。它是任何未解的方法。在我的例子中,我以为我已经将使用char *xml作为参数的声明替换为使用不必要的麻烦的const char *xml的声明,但相反,我创建了一个新的声明,并将另一个声明保留在原处。

FWIW我能够避免以下错误:

 ld: /usr/local/lib/libvmaf.a(svm.cpp.o):(.data.rel.ro._ZTI7QMatrix[_ZTI7QMatrix]+0x0): undefined reference to `vtable for __cxxabiv1::__class_type_info'

当将“C”项目与“c++”项目中的静态库链接时,通过在链接参数中添加-lstdc++。它是gcc -lstdc++,现在可以了。

最常见的方法是将-lstdc++添加到库的pkgconfig .pc文件库列表中。或者改用g++链接。

解释为什么虚表可能丢失,以及如何修复它。答案很长,因为它解释了为什么编译器可能会忘记创建虚表。(编辑)

什么是虚表?

在尝试修复错误消息之前,了解错误消息所谈论的内容可能是有用的。我将从高水平开始,然后再深入到更详细的细节。这样,一旦人们熟悉了虚变量,就可以跳过前面的内容。现在有一群人在前面跳。对于那些留下来的人:

A vtable is basically the most common implementation of polymorphism in C++. When vtables are used, every polymorphic class has a vtable somewhere in the program; you can think of it as a (hidden) static data member of the class. Every object of a polymorphic class is associated with the vtable for its most-derived class. By checking this association, the program can work its polymorphic magic. Important caveat: a vtable is an implementation detail. It is not mandated by the C++ standard, even though most (all?) C++ compilers use vtables to implement polymorphic behavior. The details I am presenting are either typical or reasonable approaches. Compilers are allowed to deviate from this!

每个多态对象都有一个(隐藏的)指向对象最派生类虚表的指针(在更复杂的情况下,可能有多个指针)。通过查看指针,程序可以判断对象的“真实”类型是什么(除了在构造过程中,但让我们跳过这种特殊情况)。例如,如果类型A的对象不指向A的虚表,那么该对象实际上是派生自A的某个对象的子对象。

The name "vtable" comes from "virtual function table". It is a table that stores pointers to (virtual) functions. A compiler chooses its convention for how the table is laid out; a simple approach is to go through the virtual functions in the order they are declared within class definitions. When a virtual function is called, the program follows the object's pointer to a vtable, goes to the entry associated with the desired function, then uses the stored function pointer to invoke the correct function. There are various tricks for making this work, but I won't go into those here.

在哪里/何时生成虚表?

A vtable is automatically generated (sometimes called "emitted") by the compiler. A compiler could emit a vtable in every translation unit that sees a polymorphic class definition, but that would usually be unnecessary overkill. An alternative (used by gcc, and probably by others) is to pick a single translation unit in which to place the vtable, similar to how you would pick a single source file in which to put a class' static data members. If this selection process fails to pick any translation units, then the vtable becomes an undefined reference. Hence the error, whose message is admittedly not particularly clear.

类似地,如果选择过程确实选择了一个翻译单元,但是该目标文件没有提供给链接器,那么虚表将成为一个未定义的引用。不幸的是,在这种情况下,错误消息甚至比选择过程失败的情况更不清楚。(感谢那些提到这种可能性的受访者。否则我可能已经忘记了。)

The selection process used by gcc makes sense if we start with the tradition of devoting a (single) source file to each class that needs one for its implementation. It would be nice to emit the vtable when compiling that source file. Let's call that our goal. However, the selection process needs to work even if this tradition is not followed. So instead of looking for the implementation of the entire class, let's look for the implementation of a specific member of the class. If tradition is followed – and if that member is in fact implemented – then this achieves the goal.

The member selected by gcc (and potentially by other compilers) is the first non-inline virtual function that is not pure virtual. If you are part of the crowd that declares constructors and destructors before other member functions, then that destructor has a good chance of being selected. (You did remember to make the destructor virtual, right?) There are exceptions; I'd expect that the most common exceptions are when an inline definition is provided for the destructor and when the default destructor is requested (using "= default").

精明的人可能会注意到,允许多态类为其所有虚函数提供内联定义。这难道不会导致选择过程失败吗?在旧的编译器中是这样的。我听说最新的编译器已经解决了这个问题,但是我不知道相关的版本号。我可以尝试查找这个,但它更容易要么编码绕过它或等待编译器抱怨。

总之,有三个关键原因导致“未定义的引用虚表”错误:

成员函数缺少定义。 目标文件未被链接。 所有虚函数都有内联定义。

这些原因本身不足以导致错误。相反,这些是解决错误的方法。不要期望故意创建这些情况之一一定会产生此错误;还有其他要求。请期望解决这些情况将解决此错误。

(好吧,当这个问题被问到时,第3条可能已经足够了。)

如何修复错误?

欢迎回来,跳过前面的人!:)

Look at your class definition. Find the first non-inline virtual function that is not pure virtual (not "= 0") and whose definition you provide (not "= default"). If there is no such function, try modifying your class so there is one. (Error possibly resolved.) See also the answer by Philip Thomas for a caveat. Find the definition for that function. If it is missing, add it! (Error possibly resolved.) If the function definition is outside the class definition, then make sure the function definition uses a qualified name, as in ClassName::function_name. Check your link command. If it does not mention the object file with that function's definition, fix that! (Error possibly resolved.) Repeat steps 2 and 3 for each virtual function, then for each non-virtual function, until the error is resolved. If you're still stuck, repeat for each static data member.

例子 具体操作的细节可能会有所不同,有时还会分成不同的问题(比如什么是未定义的引用/未解决的外部符号错误以及如何修复它?)不过,我将提供一个在特定情况下该如何做的示例,这可能会使新程序员感到困惑。

步骤1提到修改类,使其具有特定类型的函数。如果这个功能的描述超出了你的理解范围,那么你可能正处于我想要解决的情况。记住,这是实现目标的一种方式;这不是唯一的方法,在你的具体情况下,很容易有更好的方法。让我们称你的类为a。你的析构函数(在你的类定义中)声明为两者之一

virtual ~A() = default;

or

virtual ~A() {}

? 如果是这样,两个步骤将把析构函数更改为我们想要的函数类型。首先,将该行更改为

virtual ~A();

其次,在项目的源文件中放入以下一行(最好是类实现的文件,如果你有的话):

A::~A() {}

这使得你的(虚拟)析构函数是非内联的,并且不是由编译器生成的。(请随意修改内容以更好地匹配您的代码格式风格,例如在函数定义中添加头注释。)

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