对于'xxx'的虚函数表未定义引用

38
takeaway.o: In function `takeaway':
project:145: undefined reference to `vtable for takeaway'
project:145: undefined reference to `vtable for takeaway'
takeaway.o: In function `~takeaway':
project:151: undefined reference to `vtable for takeaway'
project:151: undefined reference to `vtable for takeaway'
takeaway.o: In function `gameCore':
project.h:109: undefined reference to `gameCore<int>::initialData(int)'
collect2: ld returned 1 exit status
make: *** [takeaway] Error 1

我经常从链接器收到这个错误,我知道这与内联函数暂时存储vtable有关。但具体含义我不太确定。我想它可能与我在takeaway.cpp的初始化列表中调用gameCore构造函数的方式有关。

我有一个模板类(gameCore.h)和一个从gameCore继承的类(takeaway.cpp)。vtable错误被调用了3次: 1)在takeaway的构造函数中 2)在takeaway的析构函数中 3)在gameCore的构造函数中

我正在使用G++编译器,以下是代码(我知道它可能看起来很难读,但我已经标记出错误出现的确切位置):

takeaway.h

#ifndef _TAKEAWAY_H_
#define _TAKEAWAY_H_
#include<map>
#include<cctype>
#include<stack>
#include<map>
#include<iostream>
#include<string>
#include<cstdlib>
#include"gameCore.h"
#include<vector>
using namespace std;
class takeaway : public gameCore<int>
{
 private:

 public:
// template<class Penny>
 void  textualGame();
 bool isNum(string str);
// template<class Penny>
 stack<int> initialData(int initial);
// template<class Position>
 int score (int position);
// template<class Position>
 stack<int> addStack(int currentPos, stack<int> possiblePositions);
// template<class Penny>
 takeaway (int initial);
// template<class Position>
 ~takeaway();
};
bool isNum(string str);
int charToint(char *theChar);
#endif

外卖.cpp

/*
Description :
    This game communicates with the gameCore class to determine the results
    of a game of takeaway played between two computers or a computer and human.   
*/

#include "takeaway.h"

 /*
 Description:Creates a stack represening initial data
 Note:Change to a vector eventually
 return : stack of int
 */
 stack<int> takeaway:: initialData(int initial){
   stack<int> returnStack;
   int theScore = score(initial);
   int final;
   if(initial ==0)
   {
    final = 1;
   }
   else
   {
    final = 0;
   }
   returnStack.push(theScore);
   returnStack.push(final);
   return returnStack;
 }


 /*
 Description: a textual representation of the game
 Note: This is still terribly wrong
 */

 void textualGame(){
  cout <<"this is the best i could do for a graphical representation";

 }
 /*
 Description: Deetermines if a number is even
 Note: Helper function for determining win or loss positions
 Returns: 1 if it is and 0 if it is not
 */
 int takeaway::score(int position){
  if(position % 2 == 0)
  {
     return 1;
  }
  return 0;
 }
 /*
   Description: Will return a stack , withouth the given postion in it
   will contain all positions possible after the given position
   along with anyother that wehre in the given stack.This function
   Must also update the map to represent updated positions
   Takes: a position to check and a stack to return
   Returns: A stack of possible positions.

 */
 stack<int>  takeaway::addStack(int currentPos, stack<int> possiblePositions ){
  if(currentPos != 0)
  {
    // If even
    if( currentPos % 2 == 0)
    { 
       // Create a data aray with score of the new positon and mark it as not final
    int data[] = {score(currentPos/2),0};
    vector<int> theData(data, data+sizeof(data));
        int pos = currentPos/2;
       // Add it to the map
       //this -> gamesMap[currentPos/2] = dataArray; 
       this -> gamesMap.insert(std::pair<int, vector<int> >(pos, theData));
       // Add it to the possible positions
       possiblePositions.push(pos);
    }
    if(currentPos % 3 == 0)
    {

    int data[] = {score(currentPos/3),0};
       vector<int> theData(data,data+sizeof(data));
       int  pos = currentPos/3;
       //this -> gamesMap[currentPos/3] = dataArray; 
       this -> gamesMap.insert(std::pair<int, vector<int> >(pos, theData));
       possiblePositions.push(pos);
    }
    // Work for the position that represents taking one penny
    int minusFinal = 0;
    if(currentPos - 1 == 0)
    {
      minusFinal = 1;
    }
    int data[] = {score(currentPos - 1),minusFinal};
    vector<int> theData(data,data+sizeof(data));
    int pos  = currentPos - 1;
   // this -> gamesMap[currentPos -1] = dataArary
    this->gamesMap.insert(std::pair<int,vector<int> >(pos, theData));
    possiblePositions.push(pos);
  }
  return possiblePositions;

 }
 /*
 Description: Constructor for the takeaway game
OA takes: a initial position, and initial data for it

 */
 takeaway::takeaway(int initial):gameCore<int>::gameCore(initial){ //<--- ERROR HERE
 //Constructor
 }
 /*
 Description: Destuctor
 */
 takeaway::~takeaway(){ // <--------------------- ERROR HERE
 //Destructor
 }


//checks input and creates game.
int main(int argc, char* argv[]){
  int numberPennies ;
  string game = argv[0];
  if(argc == 2 && isNum(argv[1]) )
  {
    int pennies = charToint(argv[1]);
     takeaway gameInstance(pennies ); // Creates a instance of $
  }
 //  else if(argc == 3 && argv[1] == "play" && isNum(argv[2]) )
 // {
 //   int pennies = charToint(argv[2]);
 //   takeaway<int> gameInstance(pennies); // Craete a human playab$
 // }
  else
  {
    cerr << "Error->Usage: " << game <<" [play] numberOfPennies \n";
    exit (1);
  }
 return 0;
 }

//Converts a char to a integer
int charToint(char *theChar){
  int theInt = atoi(theChar);
  return theInt;
}
 //Determines if a string is numeric
bool isNum(string str){ 
  for(int i = 0;i < str.length() ;i++){
   if(isdigit(str[i]) != 1)
   {
     cerr << "Error->Input: Number must be a Positive Integer the charecter '" << str[i]<< "' invalidated your input. \n" ;
     exit(1);
     return false;
   }
  }
  return true;
}

游戏核心.h

/*
gameCore.h

Description:
    This class created gameMap that are written as a template
    They will communicate with the specific game and the algorithm
    To keep track of positions ans there values.
*/
#ifndef GAMECORE_H
#define GAMECORE_H
#include <map>
#include <stack>
#include <string>
#include <vector>
using namespace std;


template <class Position>
class gameCore
{
 protected:
    //Best Move used by algorithim
    Position bestMove;
    //The current highest score used by the algorithim
    int highestScore ;
    //Stack to be used to remmeber what move created the score
    stack<Position> movedFrom;
    //Stack used for the algorithim.
    stack<Position> curWorkingPos;
    //The actual Map that the data will be held in.
    map<Position,vector<int> > gamesMap;
 public:

    /*
    Description : finds the data array for a poisition
    takes: a Position
    Returns: a array of integers /**
    */
    virtual stack<int> initialData(Position pos) = 0;
        /*
    Description: Game must implement a way to determine a positions
    score.

    */
        virtual int score(Position pos) = 0;
        /*
    Description: A Graphical representation of the game

    */
    virtual void textualGame() = 0;

    /*
    Description: a virtual function implemented by the child class
    it will return a stack without the given position in it.This stack
    will contain all positions available from the given postion as well as 
    all position already in the given stack. Also it will update the map with
    all generated positions.
    TAkes: a postion to check and a stack of currently working positons.

    */
    virtual stack<Position> addStack(Position currentPos, stack<Position> possiblePositions ) = 0;
    /*
       Description:Constructor that
       Creates a Map with positions as the key.
       And an array of two integers that represent the positions
       value and if we have moved here in the past.
       Takes: a Initial Position and a Array of integers
    */
    gameCore(Position initial){              // <-----ERROR HERE
       //Determine the initial data and add it to the map and queue.
       stack<int> theData = initialData(initial);
       int first = theData.top();
           theData.pop();
           int second = theData.top();
       theData.pop();
       int initialData[] = {first,second};
           vector<int> posData(initialData,initialData+sizeof(initialData));
       gamesMap[initial] = posData;
       curWorkingPos.push(initial);
    }
    /*
    Description:
       A destructor for the class
    */
     ~gameCore(){
        //I do nothing but , this class needs a destructor

    }
    /*
       Description: Takes the current position and returns 
       that positions Score.
       Takes: A position 
       Returns:A integer that is a positions score.

    */
    int getPosScore(Position thePos) const {
        return this ->gamesMap.find(thePos)->second[0];
    }
    /*
    Description: Adds values to a stack based on the current position
    Takes: a poistion
    */
    void updateStack(Position curPos){
        this ->curWorkingPos =addStack(curPos,this ->curWorkingPos ); // get a stack from the game
        // The game has a function that takes a position and a stack and based on the positions returns a stack identical to the last but with added values that represent valid moves from the postion./
    }
    /*
       Description : Takes a positions and returns a integer
       that depends on if the position is a final pos or not
       Takes: A position
       Returns: A Bool that represents if the position is a final(1)  or not (0).

    */
        // Possible change
    bool isFinal(Position thePos) {     
        typename map<Position,vector<int> >::iterator iter =  this ->gamesMap.find(thePos);
        return iter->second[1] == 1 ;
    }
    /*
    Description: Based on the given position determine if a move needs to be made.
    (if not this is a end game position and it will return itself) If a move needs
    to be made it will return the position to move to that is ideal.
    Note: (because all positions can be represented as integers for any game , the return
    type is a integer)

    */
    int evaluatePosition(Position possiblePosition ){
           if(isFinal(possiblePosition)) //If this is a final position
        {
           return  getPosScore(possiblePosition);  //Return the score 
        }
           else
           {
         updateStack(possiblePosition); //Put all possible positions from this in thte stack
         while(this -> curWorkingPos.size() != 0)
         {
           this -> movedFrom.push(this->curWorkingPos.front()); //take the top of the possible positions stack and set it the the moved from stack
           this -> curWorkingPos.pop();
           int curScore =  evaluatePosition(this ->movedFrom.top());  //Recursive call for school
           curScore = curScore * -1; //Negate the score
           if(curScore > this -> highestScore) // if the score resulting from this position is biggest seen
           {
             highestScore = curScore;
             this ->movedFrom.pop();  //do this first to get rid of the the lowest point
             this -> bestMove = this ->movedFrom.top();  // mark where the lowest point came from
           }
          else
           {
             this -> movedFrom.pop(); 
           }
         }
           }
        return this -> bestMove;
    }
    //A Structure to determine if a position has a lower value than the second
    struct posCompare{
        bool operator() (Position pos1,Position pos2) const {
            return (pos1.getPosScore() < pos2.getPosScore());
            }
        };
};
#endif

32
请提供示例,而不仅仅是贴出所有代码。 - Puppy
6
这是一个很好的问题,但是代码太长了,请尝试缩小范围,让我们能够更容易地理解正在发生的事情和问题。 - Grammin
9
你能否尝试将你的代码缩减成一个小例子来复现问题?这会帮助其他人帮助你,甚至可能帮助你自己找到问题所在。另外,在这里真的有必要留下修订历史注释吗? - R. Martinho Fernandes
2
我想补充一点,有时候出现这个错误是因为派生类的析构函数没有被定义。 - Nehal J Wani
天啊,这个问题怎么会有这么多赞。如果只提供错误日志,那么@bdonlan的回答中的第一句话可能会更好地为用户服务。 - Jendas
可能是Undefined reference to vtable的重复问题。 - jww
8个回答

35

你的一个或多个 .cpp 文件没有被链接,或者某个类中一些非内联函数没有被定义。特别是,无法找到 takeaway::textualGame() 的实现。注意,你在顶层定义了一个 textualGame(),但这与 takeaway::textualGame() 的实现是不同的 - 你可能只是忘记了在那里加上 takeaway::

错误的意思是链接器找不到一个类的“虚表”- 每个带有虚函数的类都有一个与之关联的“虚表”数据结构。在 GCC 中,该虚表是在与类的第一个列出的非内联成员相同的 .cpp 文件中生成的; 如果没有非内联成员,它将在你实例化该类的地方生成。因此,你可能没有链接具有该第一个列出的非内联成员的 .cpp 文件,或者根本没有定义该成员。


一个 cpp 文件没有被链接的原因是因为有人忘记将其添加到构建中。在我的情况下,编辑实现后发现对象在链接开始之前没有被重新构建!(在注意到它没有被编译或链接之前,我已经反复检查了该 cpp 文件的内容) - sage

29
首先,第一组错误是由于您没有实现 takeaway::textualGame() 而导致的缺少 vtable。相反,您实现了一个非成员函数 textualGame()。我认为添加缺失的 takeaway:: 将修复这个问题。
最后一个错误的原因是,在 gameCore 的构造函数中调用虚函数 initialData()。在这个阶段,虚函数根据当前正在构造的类型(gameCore),而不是最终派生类(takeaway)进行分派。这个特定的函数是纯虚的,因此在这里调用它会产生未定义的行为。
有两种可能的解决方案: - 将 gameCore 的初始化代码从构造函数中移出,并放到一个单独的初始化函数中,在对象完全构造后必须调用该函数。 - 将 gameCore 分成两个类:一个由 takeaway 实现的抽象接口,和一个包含状态的具体类。首先构造 takeaway,然后将其(通过对接口类的引用)传递给具体类的构造函数。
我建议采用第二种方法,因为它是向更小的类和更松散的耦合的转移,并且更难以错误地使用这些类。第一种方法更容易出错,因为没有办法确保初始化函数正确调用。
最后一个要点:基类的析构函数通常应该是虚拟的(以允许多态删除)或受保护的(以防止无效的多态删除)。

这确实是一个问题,但不是 OP 错误的原因。 - bdonlan
@bdonlan:这是最后一个错误的原因,由于尝试调用没有实现的gameCore::initialData()而引起。 - Mike Seymour
不是的 - 这是一个链接时错误。它对运行时初始化顺序一无所知。如果发生这样的错误,它将在编译时或运行时发生。 - bdonlan
@bdonlan: 编译器在 gameCore 的构造函数期间生成对 gameCore::initialData() 的调用,因为它是活动的重载。它不知道该函数是否已实现,因此无法在这个阶段生成错误。链接器发现该函数未实现,并生成我们看到的错误。 - Mike Seymour
啊,我明白你的意思了 - 这是一个“纯”的虚函数,所以通常没有实现。编译器生成对纯虚函数实现的直接调用(通常不存在),因此链接器会出现错误。虽然从技术上讲,在另一个翻译单元中可以为纯虚拟提供实现,但编译器无法出现错误…… - bdonlan

7

如果一个类在类外定义了虚函数,那么g++只会在包含第一个类外虚函数定义的目标文件中生成vtable:

//test.h
struct str
{
   virtual void f();
   virtual void g();
};

//test1.cpp
#include "test.h"
void str::f(){}

//test2.cpp
#include "test.h"
void str::g(){}

虚函数表将在test1.o中,但不在test2.o中。

这是g++实现的一种优化,以避免编译在类内定义的虚方法被vtable引用。

您描述的链接错误表明,在您的项目中缺少虚方法的定义(例如上面的str :: f)。


3
您可以查看这个与您问题相同的答案(据我所知): https://dev59.com/5XM_5IYBdhLWcg3wQQpd#1478553 那里发布的链接解释了问题。
为了快速解决您的问题,您应该尝试编写类似于以下内容的代码: ImplementingClass :: virtualFunctionToImplement() {...} 它帮助了我很多。

2

类中函数缺少实现

我遇到这个问题的原因是我从cpp文件中删除了函数的实现,但是忘记从.h文件中删除声明。

我的答案并没有具体回答你的问题,但让来到这个帖子寻找答案的人们知道这也可能是一个原因。


1

这意味着您未能链接显式实例化的基类型public gameCore(而头文件中仅是前向声明)。

由于我们对您的构建配置/库依赖关系一无所知,因此无法确定缺少哪些链接标志/源文件,但我希望这个提示能够帮助您解决问题。


0
如果你有虚拟析构函数,你需要像这样编写它: ~SubListener() override = default;,不要忘记这个=default

0
GNU 链接器,在我的情况下是 GCC 8.1.0 的伴侣,很好地检测到未重新声明的纯虚拟方法,但在某些复杂的类设计上面,它无法识别方法缺失的实现,并以平淡的“V-Table 缺失”作为答案,甚至倾向于报告丢失的实现,尽管实现确实存在。
那么唯一的解决方案就是逐个方法手动验证实现的声明一致性。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接