Referência indefinida à vtable

357

Ao criar meu programa C ++, estou recebendo a mensagem de erro

referência indefinida a 'vtable ...

Qual é a causa desse problema? Como faço para corrigir isso?


Acontece que estou recebendo o erro do código a seguir (a classe em questão é CGameModule.) E durante toda a minha vida não consigo entender qual é o problema. No começo, pensei que estava relacionado ao esquecimento de atribuir um corpo a uma função virtual, mas, pelo que entendi, tudo está aqui. A cadeia de herança é um pouco longa, mas aqui está o código-fonte relacionado. Não tenho certeza de quais outras informações devo fornecer.

Nota: Parece que o construtor está onde este erro está acontecendo.

Meu código:

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;
};

Herda de ...

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;
};

Que herda de ....

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
RyanG
fonte
Qual função está lançando a "referência indefinida à vtable ..."?
J. Polfer
3
Perdi totalmente que a mensagem de erro especifica uma função. Por acaso é o construtor, então vi o nome da minha classe e não fiz a conexão. Então, o construtor está jogando isso. Vou adicionar esse detalhe ao meu post original.
perfil completo de RyanG
3
Se você não reconstruiu os arquivos do projeto depois de fazer alterações significativas (por exemplo, qmake -projecte depois qmake) para gerar um novo Makefile, essa é uma provável fonte do erro ao usar o Qt.
David C. Rankin
@ DavidC.Rankin, outro problema relacionado ao Qt é que, se o arquivo com Q_OBJECTé copiado externamente, mas ainda não faz parte do arquivo .pro, embora seja compilado corretamente, ele não vincula. Temos que adicionar esse .h/.cpparquivo no arquivo .pro para podermos qmake.
iammilind

Respostas:

420

As Perguntas frequentes do GCC têm uma entrada:

A solução é garantir que todos os métodos virtuais não puros sejam definidos. Observe que um destruidor deve ser definido mesmo se for declarado puro-virtual [class.dtor] / 7.

Alexandre Hamez
fonte
17
nm -C CGameModule.o | grep CGameModule::listará os métodos definidos, assumindo que toda a implementação da classe vá para o arquivo de objeto lógico. Você pode comparar isso com o que é definido como virtual para descobrir o que você perdeu.
Troy Daniels
133
FFS, por que o compilador não verifica isso e imprime uma mensagem de erro?
Lenar Hoyt
20
Obviamente, isso só pode ser descoberto pelo vinculador, não pelo compilador.
Xoph
2
No meu caso, tínhamos classe abstrata que não tinha implementação destruidora. Eu tive que colocar a implementação vazia ~ MyClass () {}
Shefy Gur-ary 11/11/16
11
Você pode obter um erro como este, quando os objetos que você está tentando vincular está faltando no (arquivo libxyz.a) de arquivo: undefined reference to `vtable para objfilename'
Kemin Zhou
162

Pelo que vale a pena, esquecer um corpo em um destruidor virtual gera o seguinte:

referência indefinida a `vtable para CYourClass '.

Estou adicionando uma observação porque a mensagem de erro é enganosa. (Isso ocorreu com a versão 4.6.3 do gcc.)

Dan
fonte
23
Eu tive que colocar explicitamente o corpo do meu destruidor virtual vazio no arquivo de definição (* .cc). Tê-lo no cabeçalho ainda me deu o erro.
PopcornKing
4
Observe que depois de adicionar o destruidor virtual ao arquivo de implementação, o gcc me disse o erro real, que estava faltando em outra função.
Moodboom 16/07/2015
11
@PopcornKing Vi o mesmo problema. Mesmo definir ~Destructor = default;no arquivo de cabeçalho não ajudou. Existe um bug documentado arquivado no gcc?
RD
isso pode ser um problema diferente, mas meu problema não era ter uma implementação para um destruidor não virtual (estava mudando para ponteiros exclusivos / compartilhados e o removia do arquivo de origem, mas não tinha uma "implementação" no cabeçalho )
svenevs
isso resolveu meu problema, adicionando um corpo {} vazio ao destruidor virtual evitou o erro.
Bogdan Ionitza 19/11/19
56

Então, eu descobri o problema e era uma combinação de lógica ruim e não estar totalmente familiarizado com o mundo das ferramentas automáticas / automáticas. Eu estava adicionando os arquivos corretos ao meu modelo Makefile.am, mas não tinha certeza de qual etapa do nosso processo de criação realmente criou o próprio makefile. Então, eu estava compilando com um makefile antigo que não fazia ideia dos meus novos arquivos.

Obrigado pelas respostas e pelo link para as Perguntas frequentes do GCC. Certificarei-me de ler isso para evitar que esse problema ocorra por um motivo real.

RyanG
fonte
43
Em resumo: o .cpp simplesmente não foi incluído na compilação. A mensagem de erro é realmente enganosa.
Offirmo
67
Para usuários do Qt: você pode obter esse mesmo erro se esquecer de colocar um cabeçalho no moc.
Chris Morlier
8
Eu acho que você deveria aceitar a resposta de Alexandre Hamez. As pessoas que pesquisam esse erro provavelmente precisam da solução dele em vez da sua.
Tim
13
-1 Essa pode ser a solução para o seu problema, mas não é uma resposta para a pergunta original. A resposta correta é simplesmente que você não forneceu um arquivo de objeto com os símbolos necessários. Por que você não os forneceu é outra história.
294 Walter
12
@ Walter: Na verdade, essa foi a resposta exata que eu estava procurando. Os outros são óbvios e, portanto, inúteis.
Edgar Bonet
50

Se você estiver usando o Qt, tente executar novamente o qmake. Se esse erro estiver na classe do widget, qmake pode ter falhado em perceber que a tabela de classe da interface do usuário deve ser regenerada. Isso corrigiu o problema para mim.

gluk47
fonte
2
Acabei de excluir a pasta inteira com a compilação que também funcionou.
Tomáš Zato - Restabelece Monica
Isso não é exclusivo qmake, eu tive o mesmo com cmake. Parte do problema pode ser que ambas as ferramentas têm um problema com os arquivos de cabeçalho, que nem sempre acionam uma reconstrução quando necessário.
MSalters 23/08/16
2
Pensei em "Reconstruir" o reran qmake automaticamente ... aparentemente não. Fiz "Run qmake" por sua sugestão, depois "Rebuild" e isso resolveu o meu problema.
yano 12/06
45

Referências indefinidas à vtable também podem ocorrer devido à seguinte situação. Apenas tente o seguinte:

Classe A Contém:

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

Classe B Contém:

  1. A definição para a função acimaA.
  2. A definição para a função acimaB.

A classe C contém: Agora você está escrevendo uma classe C na qual a derivará da classe A.

Agora, se você tentar compilar, obterá uma referência indefinida ao vtable para a classe C como erro.

Razão:

functionAé definido como virtual puro e sua definição é fornecida na Classe B. functionBé definida como virtual (NÃO PURE VIRTUAL); portanto, tenta encontrar sua definição na própria Classe A, mas você forneceu sua definição na Classe B.

Solução:

  1. Tornar a função B como virtual pura (se você tiver requisitos assim) virtual void functionB(parameters) =0; (isso funciona, é testado)
  2. Forneça a definição para functionB na própria classe A, mantendo-a como virtual. (Espero que funcione, porque eu não tentei isso)
Srinivasa Lakkaraju
fonte
@ ilya1725 Sua edição sugerida não está apenas corrigindo a formatação e similares, você também está alterando a resposta, por exemplo, dizendo que a classe C é derivada de B em vez de A, e você está alterando a segunda solução. Isso muda substancialmente a resposta. Nesses casos, deixe um comentário para o autor. Obrigado!
Fabio diz Reinstate Monica
@FabioTurati de que classe classeC é herdada a partir de então? A frase não é clara. Além disso, qual é o significado de "Classe C contém:"?
achou
@ ilya1725 Esta resposta não é muito clara e não sou contra editá-la e melhorá-la. O que estou dizendo é que sua edição altera o significado da resposta e é uma alteração muito drástica. Felizmente, o autor intervém e esclarece o que ele quis dizer (embora ele esteja inativo há muito tempo).
Fabio diz Reinstate Monica
Obrigado! No meu caso, eu tinha apenas 2 classes na minha hierarquia. A classe A declarou um método virtual puro. A declaração da classe B afirmou que substituiria esse método, mas ainda não havia escrito a definição do método substituído.
Nick Desaulniers
44

Eu simplesmente recebi esse erro porque meu arquivo cpp não estava no makefile.

Vai
fonte
De fato, parece que a mensagem muda ligeiramente do habitual undefined reference to {function/class/struct}quando há virtualcoisas envolvidas. Me jogou fora.
Keith M
30

O que é um vtable?

Pode ser útil saber do que a mensagem de erro está falando antes de tentar corrigi-la. Vou começar em um nível alto e depois trabalhar com mais alguns detalhes. Dessa forma, as pessoas podem pular adiante assim que se sentirem confortáveis ​​com o entendimento das tabelas vt. ... e lá vai um monte de pessoas pulando adiante agora. :) Para aqueles que ficam por aqui:

Uma vtable é basicamente a implementação mais comum de polimorfismo em C ++ . Quando vtables são usadas, toda classe polimórfica tem uma tabela em algum lugar do programa; você pode pensar nisso como um staticmembro de dados (oculto) da classe. Todo objeto de uma classe polimórfica é associado ao vtable para sua classe mais derivada. Ao verificar essa associação, o programa pode trabalhar sua mágica polimórfica. Advertência importante: uma vtable é um detalhe de implementação. Não é obrigatório pelo padrão C ++, embora a maioria dos compiladores C ++ (todos?) Use vtables para implementar comportamento polimórfico. Os detalhes que estou apresentando são abordagens típicas ou razoáveis. Compiladores podem se desviar disso!

Cada objeto polimórfico possui um ponteiro (oculto) para a vtable para a classe mais derivada do objeto (possivelmente vários ponteiros, nos casos mais complexos). Observando o ponteiro, o programa pode dizer qual é o tipo "real" de um objeto (exceto durante a construção, mas vamos pular esse caso especial). Por exemplo, se um objeto do tipo Anão apontar para a vtable de A, então esse objeto é realmente um subobjeto de algo derivado A.

O nome "vtable" vem de " v irtual function table ". É uma tabela que armazena ponteiros para funções (virtuais). Um compilador escolhe sua convenção de como a tabela é organizada; Uma abordagem simples é analisar as funções virtuais na ordem em que são declaradas nas definições de classe. Quando uma função virtual é chamada, o programa segue o ponteiro do objeto até uma tabela, vai para a entrada associada à função desejada e usa o ponteiro da função armazenada para chamar a função correta. Existem vários truques para fazer isso funcionar, mas não vou abordar esses aqui.

Onde / quando é vtablegerado?

Uma vtable é gerada automaticamente (às vezes chamada de "emitida") pelo compilador. Um compilador pode emitir uma vtable em todas as unidades de tradução que veem uma definição de classe polimórfica, mas isso geralmente seria um exagero desnecessário. Uma alternativa ( usada pelo gcc e provavelmente por outros) é escolher uma única unidade de tradução na qual colocar a vtable, semelhante a como você escolheria um único arquivo de origem para colocar os membros de dados estáticos de uma classe. Se esse processo de seleção falhar na escolha de quaisquer unidades de tradução, a tabela v se torna uma referência indefinida. Daí o erro, cuja mensagem não é reconhecidamente clara.

Da mesma forma, se o processo de seleção escolher uma unidade de tradução, mas esse arquivo de objeto não for fornecido ao vinculador, a vtable se tornará uma referência indefinida. Infelizmente, a mensagem de erro pode ser ainda menos clara nesse caso do que no caso em que o processo de seleção falhou. (Obrigado aos respondentes que mencionaram essa possibilidade. Eu provavelmente teria esquecido o contrário.)

O processo de seleção usado pelo gcc faz sentido se começarmos com a tradição de dedicar um arquivo de origem (único) a cada classe que precisa de um para sua implementação. Seria bom emitir a vtable ao compilar esse arquivo de origem. Vamos chamar isso de nosso objetivo. No entanto, o processo de seleção precisa funcionar, mesmo que essa tradição não seja seguida. Portanto, em vez de procurar a implementação de toda a classe, vamos procurar a implementação de um membro específico da classe. Se a tradição for seguida - e se esse membro for de fato implementado -, isso alcançará o objetivo.

O membro selecionado pelo gcc (e potencialmente por outros compiladores) é a primeira função virtual não embutida que não é virtual pura. Se você faz parte da multidão que declara construtores e destruidores antes de outras funções-membro, esse destruidor tem uma boa chance de ser selecionado. (Você se lembrou de tornar o destruidor virtual, certo?) Há exceções; Eu esperaria que as exceções mais comuns sejam quando uma definição em linha é fornecida para o destruidor e quando o destruidor padrão é solicitado (usando " = default").

O astuto pode perceber que uma classe polimórfica pode fornecer definições em linha para todas as suas funções virtuais. Isso não causa falha no processo de seleção? Isso acontece em compiladores mais antigos. Eu li que os compiladores mais recentes abordaram essa situação, mas não sei os números de versão relevantes. Eu poderia tentar procurar isso, mas é mais fácil codificá-lo ou aguardar a reclamação do compilador.

Em resumo, há três causas principais do erro "referência indefinida à vtable":

  1. Uma função de membro está ausente em sua definição.
  2. Um arquivo de objeto não está sendo vinculado.
  3. Todas as funções virtuais têm definições embutidas.

Essas causas são, por si só, insuficientes para causar o erro por conta própria. Em vez disso, é isso que você abordaria para resolver o erro. Não espere que a criação intencional de uma dessas situações definitivamente produza esse erro; existem outros requisitos. Espere que a solução dessas situações resolva esse erro.

(OK, o número 3 pode ter sido suficiente quando essa pergunta foi feita.)

Como corrigir o erro?

Bem-vindo de volta, pessoas pulando à frente! :)

  1. Veja a sua definição de classe. Encontre a primeira função virtual não embutida que não é virtual pura (não " = 0") e cuja definição você fornece (não " = default").
    • Se não houver essa função, tente modificar sua classe para que exista uma. (Erro possivelmente resolvido.)
    • Veja também a resposta de Philip Thomas para uma advertência.
  2. Encontre a definição para essa função. Se estiver faltando, adicione-o! (Erro possivelmente resolvido.)
  3. Verifique seu comando de link. Se ele não mencionar o arquivo de objeto com a definição dessa função, corrija isso! (Erro possivelmente resolvido.)
  4. Repita as etapas 2 e 3 para cada função virtual e, em seguida, para cada função não virtual, até que o erro seja resolvido. Se você ainda estiver travado, repita para cada membro de dados estático.

Exemplo
Os detalhes do que fazer podem variar e às vezes se ramificam em perguntas separadas (como O que é uma referência indefinida / erro de símbolo externo não resolvido e como faço para corrigi-lo? ). Entretanto, fornecerei um exemplo do que fazer em um caso específico que pode atrapalhar os programadores mais novos.

A Etapa 1 menciona a modificação de sua classe para que ela tenha uma função de um determinado tipo. Se a descrição dessa função passou por sua cabeça, você pode estar na situação que pretendo abordar. Lembre-se de que essa é uma maneira de atingir a meta; não é o único caminho, e facilmente poderia haver caminhos melhores em sua situação específica. Vamos ligar para a sua turma A. Seu destruidor é declarado (na sua definição de classe) como

virtual ~A() = default;

ou

virtual ~A() {}

? Nesse caso, duas etapas mudarão seu destruidor para o tipo de função que queremos. Primeiro, mude essa linha para

virtual ~A();

Segundo, coloque a seguinte linha em um arquivo de origem que faça parte do seu projeto (de preferência o arquivo com a implementação da classe, se você tiver um):

A::~A() {}

Isso faz com que seu destruidor (virtual) não esteja em linha e não seja gerado pelo compilador. (Sinta-se à vontade para modificar as coisas para corresponder melhor ao seu estilo de formatação de código, como adicionar um comentário de cabeçalho à definição da função.)

JaMiT
fonte
Ah, bravo! Para uma explicação muito detalhada e muito bem definida.
David C. Rankin
Tive que rolar para baixo para longe para ler isso. Tenha um voto positivo pela excelente explicação!
Thomas
24

Há muita especulação acontecendo em várias respostas aqui. Abaixo, darei um código mínimo que reproduz esse erro e explico por que ele está ocorrendo.

Código razoavelmente mínimo para reproduzir este erro

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;
}

Você pode compilar isso usando o GCC assim:

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

Agora você pode reproduzir o erro removendo o = 0IBase.hpp. Eu recebo este erro:

~/.../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

Explicação

Observe que o código acima não requer destruidores virtuais, construtores ou outros arquivos extras para que a compilação seja bem-sucedida (embora você deva tê-los).

A maneira de entender esse erro é a seguinte: O Linker está procurando pelo construtor do IBase. Isso será necessário para o construtor de Derivado. No entanto, como Derivado substitui os métodos do IBase, ele tem uma tabela anexável que fará referência ao IBase. Quando o vinculador diz "referência indefinida à vtable para IBase", significa basicamente que o Derived possui uma referência vtable à IBase, mas não consegue encontrar nenhum código de objeto compilado do IBase para procurar. Portanto, o ponto principal é que a classe IBase possui declarações sem implementações. Isso significa que um método no IBase é declarado como virtual, mas esquecemos de marcá-lo como virtual puro OU fornecer sua definição.

Dica de despedida

Se tudo mais falhar, uma maneira de depurar esse erro é criar um programa mínimo que seja compilado e depois alterá-lo para que ele chegue ao estado desejado. No meio, continue compilando para ver quando começa a falhar.

Nota sobre o sistema de construção ROS e Catkin

Se você estava compilando acima do conjunto de classes no ROS usando o sistema catkin build, precisará das seguintes linhas em CMakeLists.txt:

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

A primeira linha basicamente diz que queremos criar um executável chamado myclass e o código para compilar isso pode ser encontrado nos arquivos a seguir. Um desses arquivos deve ter main (). Observe que você não precisa especificar arquivos .hpp em nenhum lugar do CMakeLists.txt. Além disso, você não precisa especificar Derived.cpp como biblioteca.

Shital Shah
fonte
18

Acabei de encontrar outra causa para esse erro que você pode verificar.

A classe base definiu uma função virtual pura como:

virtual int foo(int x = 0);

E a subclasse tinha

int foo(int x) override;

O problema foi o erro de digitação que "=0"deveria estar fora dos parênteses:

virtual int foo(int x) = 0;

Portanto, caso você esteja rolando até aqui, você provavelmente não encontrou a resposta - isso é outra coisa para verificar.

Philip Thomas
fonte
12

Isso pode acontecer com bastante facilidade se você esquecer de vincular ao arquivo de objeto que possui a definição.

Hazok
fonte
11
Adicione mais algumas descrições à sua resposta e possível correção.
Mohit Jain
11
Esquecer-se do link pode incluir o esquecimento de adicionar às instruções de compilação. No meu caso, meu arquivo cpp tinha tudo perfeitamente 'definido', exceto que eu esqueci de adicionar o arquivo cpp à lista de fontes (no meu CMakeLists.txt, mas o mesmo pode acontecer em outros sistemas de compilação, como em um arquivo .pro). Como resultado, tudo compilado e então eu tenho o erro em tempo de ligação ...
sage
@Mohit Jain O link para os arquivos de objetos depende da configuração e das ferramentas do ambiente. Infelizmente, uma correção específica para uma pessoa pode ser diferente para outra (por exemplo, CMake vs ferramenta proprietária vs IDE vs etc.)
Hazok
11

O compilador GNU C ++ precisa decidir onde colocar o vtablecaso de você ter a definição das funções virtuais de um objeto espalhada por várias unidades de compilação (por exemplo, algumas das definições de funções virtuais dos objetos estão em um arquivo .cpp e outras em outro. arquivo cpp e assim por diante).

O compilador escolhe colocar o vtablemesmo local onde a primeira função virtual declarada é definida.

Agora, por algum motivo, você se esqueceu de fornecer uma definição para a primeira função virtual declarada no objeto (ou se esqueceu de adicionar o objeto compilado na fase de vinculação por engano), receberá esse erro.

Como efeito colateral, observe que somente para esta função virtual específica você não receberá o erro tradicional do vinculador como se estivesse faltando a função foo .

Iulian Popa
fonte
8

Não para atravessar post, mas. Se você está lidando com herança, o segundo hit do Google foi o que eu tinha perdido, ou seja. todos os métodos virtuais devem ser definidos.

Tal como:

virtual void fooBar() = 0;

Consulte a Referência Indefinida C ++ da answare para vtable e herança para obter detalhes. Acabei de perceber que já foi mencionado acima, mas que diabos isso pode ajudar alguém.

Victor Häggqvist
fonte
8

Ok, a solução para isso é que você pode ter perdido a definição. Veja o exemplo abaixo para evitar o erro do compilador vtable:

// In the CGameModule.h

class CGameModule
{
public:
    CGameModule();
    ~CGameModule();

    virtual void init();
};

// In the CGameModule.cpp

#include "CGameModule.h"

CGameModule::CGameModule()
{

}

CGameModule::~CGameModule()
{

}

void CGameModule::init()    // Add the definition
{

}
Sammy
fonte
7
  • Tem certeza de que CDasherComponentpossui um corpo para o destruidor? Definitivamente não está aqui - a questão é se está no arquivo .cc.
  • De uma perspectiva de estilo, CDasherModuledeve definir explicitamente seu destruidor virtual.
  • Parece que CGameModuletem um extra } no final (depois do }; // for the class).
  • Está CGameModulesendo vinculado às bibliotecas que definem CDasherModulee CDasherComponent?
Stephen
fonte
- Sim, CDasherComponent possui um corpo destruidor no cpp. Eu pensei que foi declarado no .h quando publiquei isso. - Devidamente anotado. - Esse foi um suporte extra que eu adicionei por engano ao retirar a documentação. - Pelo que entendi, sim. Eu modifiquei um arquivo de automake que não escrevi, mas tenho seguido os padrões que funcionaram para outras classes com o mesmo padrão de herança das mesmas classes, portanto, a menos que eu tenha cometido um erro estúpido (totalmente possível) , Eu não acho que é isso.
ryang
@RyanG: tente mover todas as definições de função virtual para a definição de classe. Verifique se estão todos lá e veja se o resultado muda.
Stephen
5

Talvez a falta do destruidor virtual esteja contribuindo com o fator?

virtual ~CDasherModule(){};
DevByStarlight
fonte
5

Este foi o primeiro resultado de pesquisa para mim, então pensei em adicionar outra coisa para verificar: verifique se a definição de funções virtuais está realmente na classe. No meu caso, eu tive o seguinte:

Arquivo de cabeçalho:

class A {
 public:
  virtual void foo() = 0;
};

class B : public A {
 public:
  void foo() override;
};

e no meu arquivo .cc:

void foo() {
  ...
}

Isso deve ler

void B::foo() {
}
RedSpikeyThing
fonte
3

Talvez não. Definitivamente ~CDasherModule() {}está faltando.

Bretzelus
fonte
3

Tantas respostas aqui, mas nenhuma delas parecia ter coberto o meu problema. Eu tive o seguinte:


class I {
    virtual void Foo()=0;
};

E em outro arquivo (incluído na compilação e vinculação, é claro)

class C : public I{
    void Foo() {
        //bar
    }
};

Bem, isso não funcionou e eu recebi o erro que todo mundo está falando. Para resolvê-lo, tive que mover a definição real de Foo da declaração de classe como tal:

class C : public I{
    void Foo();
};

C::Foo(){
   //bar
}

Como não sou um guru de C ++, não posso explicar por que isso é mais correto, mas resolveu o problema para mim.

Nathan Daniels
fonte
Não sou um guru de C ++, mas parece estar relacionado à mistura da declaração e da definição no mesmo arquivo, com um arquivo de definição adicional.
Terry G Lorber
11
Quando a definição da função estava dentro da sua definição de classe, foi declarada implicitamente "inline". Nesse ponto, todas as suas funções virtuais foram incorporadas. Quando você moveu a função para fora da definição de classe, ela não era mais uma função "embutida". Como você tinha uma função virtual não embutida, seu compilador sabia onde emitir a vtable. (A explicação mais completa não vai caber em um comentário.)
JaMiT
3

Então, eu estava usando o Qt com o Windows XP e o compilador MinGW e isso estava me deixando louco.

Basicamente, o moc_xxx.cpp foi gerado vazio, mesmo quando fui adicionado

Q_OBJECT

Excluir tudo que torna as funções virtuais, explícitas e o que você acha que não funciona. Por fim, comecei a remover linha por linha e verificou-se que eu tinha

#ifdef something

Ao redor do arquivo. Mesmo quando o #ifdef era verdadeiro, o arquivo moc não foi gerado.

A remoção de todos os #ifdefs corrigiu o problema.

Isso não estava acontecendo com o Windows e o VS 2013.

Daniel Georgiev
fonte
Comentar a linha Q_OBJECT fez com que meu aplicativo de teste simples fosse compilado g++ *.cpp .... (Necessário rápida algo e sujo, mas qmake estava cheio de tristeza.)
Nathan Kidd
2

Se tudo mais falhar, procure duplicação. Fui mal direcionado pela referência inicial explícita a construtores e destruidores até ler uma referência em outro post. É qualquer método não resolvido. No meu caso, pensei que havia substituído a declaração que usava char * xml como parâmetro por uma que usava o const char * xml desnecessariamente problemático, mas, em vez disso, criei uma nova e deixei a outra no lugar.

Jerry Miller
fonte
2

Existem muitas possibilidades mencionadas para causar esse erro, e tenho certeza que muitas delas causam o erro. No meu caso, havia outra definição da mesma classe, devido a uma duplicação do arquivo de origem. Esse arquivo foi compilado, mas não vinculado, portanto, o vinculador estava reclamando por não conseguir encontrá-lo.

Para resumir, eu diria que, se você ficou olhando a classe por tempo suficiente e não consegue ver o possível problema de sintaxe que pode estar causando, procure problemas de compilação, como um arquivo ausente ou um arquivo duplicado.

crw4096
fonte
2

No meu caso, estou usando o Qt e defini uma QObjectsubclasse em um arquivo foo.cpp(não .h). A correção foi adicionada #include "foo.moc"no final de foo.cpp.

Stefan Monov
fonte
2

Também acho que vale a pena mencionar que você também receberá a mensagem ao tentar vincular ao objeto de qualquer classe que tenha pelo menos um método virtual e o vinculador não pode encontrar o arquivo. Por exemplo:

Foo.hpp:

class Foo
{
public:
    virtual void StartFooing();
};

Foo.cpp:

#include "Foo.hpp"

void Foo::StartFooing(){ //fooing }

Compilado com:

g++ Foo.cpp -c

E main.cpp:

#include "Foo.hpp"

int main()
{
    Foo foo;
}

Compilado e vinculado a:

g++ main.cpp -o main

Dá o nosso erro favorito:

/tmp/cclKnW0g.o: Na função main': main.cpp:(.text+0x1a): undefined reference tovtable para Foo 'collect2: erro: ld retornou 1 status de saída

Esta ocorrência do meu entendimento, porque:

  1. O Vtable é criado por classe em tempo de compilação

  2. O vinculador não tem acesso ao vtable que está no Foo.o

Adam Stepniak
fonte
1

Eu recebi esse erro no seguinte cenário

Considere um caso em que você definiu a implementação das funções de membro de uma classe no próprio arquivo de cabeçalho. Este arquivo de cabeçalho é um cabeçalho exportado (em outras palavras, pode ser copiado para alguns arquivos comuns / incluídos diretamente na sua base de código). Agora você decidiu separar a implementação das funções de membro para o arquivo .cpp. Depois que você separou / moveu a implementação para .cpp, o arquivo de cabeçalho agora possui apenas os protótipos das funções de membro dentro da classe. Após as alterações acima, se você criar sua base de código, poderá receber o erro "referência indefinida para 'vtable ...".

Para corrigir isso, antes da compilação, exclua o arquivo de cabeçalho (no qual você fez alterações) no diretório common / include. Também certifique-se de alterar seu makefile para acomodar / adicionar o novo arquivo .o criado a partir do novo arquivo .cpp que você acabou de criar. Quando você executa essas etapas, o compilador / vinculador não reclama mais.

Sidharth Middela
fonte
estranho. Se um cabeçalho precisar ser copiado em outro lugar, o sistema de construção deve atualizar a cópia automaticamente assim que o original for modificado e antes de qualquer inclusão em outro arquivo. Se você precisar fazer isso manualmente, está ferrado.
Offirmo
1

Eu recebi esse tipo de erro nas situações em que estava tentando vincular a um objeto quando recebi um bug que impedia que o objeto fosse adicionado ao arquivo morto.

Digamos que eu tenho libXYZ.a que deveria ter bioseq.o em int, mas não possui.

Eu recebi um erro:

combineseq.cpp:(.text+0xabc): undefined reference to `vtable for bioseq'

Isso é diferente de todos os itens acima. Eu chamaria esse objeto ausente no problema de arquivamento.

Kemin Zhou
fonte
0

Também é possível que você receba uma mensagem como

SomeClassToTest.host.o: In function `class1::class1(std::string const&)':
class1.hpp:114: undefined reference to `vtable for class1'
SomeClassToTest.host.o: In function `class1::~class1()':
class1.hpp:119: undefined reference to `vtable for class1'
collect2: error: ld returned 1 exit status
[link] FAILED: 'g++' '-o' 'stage/tests/SomeClassToTest' 'object/tests/SomeClassToTest.host.o' 'object/tests/FakeClass1.SomeClassToTest.host.o'

se você esquecer de definir uma função virtual de uma classe FakeClass1 ao tentar vincular um teste de unidade a outra classe SomeClass.

//class declaration in class1.h
class class1
{
    public:
    class1()
    {
    }
    virtual ~class1()
    {
    }
    virtual void ForgottenFunc();
};

E

//class definition in FakeClass1.h
//...
//void ForgottenFunc() {} is missing here

Nesse caso, sugiro que você verifique seu falso para class1 mais uma vez. Você provavelmente descobrirá que esqueceu de definir uma função virtual ForgottenFuncem sua classe falsa.

Yuriy
fonte
0

Eu recebi esse erro quando adicionei uma segunda classe a um par de fonte / cabeçalho existente. Dois cabeçalhos de classe no mesmo arquivo .he definições de função para duas classes no mesmo arquivo .cpp.

Eu já fiz isso com sucesso antes, com aulas destinadas a trabalhar em conjunto, mas aparentemente algo não gostou de mim neste momento. Ainda não sei o que, mas dividi-los em uma classe por unidade de compilação corrigiu tudo.


A tentativa falhada:

_gui_icondata.h:

#ifndef ICONDATA_H
#define ICONDATA_H

class Data;
class QPixmap;

class IconData
{
public:
    explicit IconData();
    virtual ~IconData();

    virtual void setData(Data* newData);
    Data* getData() const;
    virtual const QPixmap* getPixmap() const = 0;

    void toggleSelected();
    void toggleMirror();
    virtual void updateSelection() = 0;
    virtual void updatePixmap(const QPixmap* pixmap) = 0;

protected:
    Data* myData;
};

//--------------------------------------------------------------------------------------------------

#include "_gui_icon.h"

class IconWithData : public Icon, public IconData
{
    Q_OBJECT
public:
    explicit IconWithData(QWidget* parent);
    virtual ~IconWithData();

    virtual const QPixmap* getPixmap() const;
    virtual void updateSelection();
    virtual void updatePixmap(const QPixmap* pixmap);

signals:

public slots:
};

#endif // ICONDATA_H

_gui_icondata.cpp:

#include "_gui_icondata.h"

#include "data.h"

IconData::IconData()
{
    myData = 0;
}

IconData::~IconData()
{
    if(myData)
    {
        myData->removeIcon(this);
    }
    //don't need to clean up any more; this entire object is going away anyway
}

void IconData::setData(Data* newData)
{
    if(myData)
    {
        myData->removeIcon(this);
    }
    myData = newData;
    if(myData)
    {
        myData->addIcon(this, false);
    }
    updateSelection();
}

Data* IconData::getData() const
{
    return myData;
}

void IconData::toggleSelected()
{
    if(!myData)
    {
        return;
    }

    myData->setSelected(!myData->getSelected());
    updateSelection();
}

void IconData::toggleMirror()
{
    if(!myData)
    {
        return;
    }

    myData->setMirrored(!myData->getMirrored());
    updateSelection();
}

//--------------------------------------------------------------------------------------------------

IconWithData::IconWithData(QWidget* parent) :
    Icon(parent), IconData()
{
}

IconWithData::~IconWithData()
{
}

const QPixmap* IconWithData::getPixmap() const
{
    return Icon::pixmap();
}

void IconWithData::updateSelection()
{
}

void IconWithData::updatePixmap(const QPixmap* pixmap)
{
    Icon::setPixmap(pixmap, true, true);
}

Novamente, adicionar um novo par de fonte / cabeçalho e cortar / colar a classe IconWithData literalmente "simplesmente funcionou".

AaronD
fonte
0

Meu caso foi bobo, tive um extra "depois #includepor engano e adivinhem?

undefined reference to vtable!

Estive coçando a cabeça e o rosto por horas comentando funções virtuais para ver se alguma coisa mudou e, finalmente, removendo o extra " , tudo foi consertado! Esse tipo de coisa realmente precisa resultar em erro de compilação e não erro de link.

Por extra ", quero dizer:

#include "SomeHeader.h""
Bahram Pouryousefi
fonte
0

No meu caso, eu tinha uma classe base chamada Pessoa e duas classes derivadas chamadas Estudante e Professor.

Como meu programa foi corrigido foi: 1. Fiz todas as funções na classe base Pure Virtual. 2. Usei todos os destruidores virtuais comodefault ones.

Gaurav Kumar
fonte
-3

Eu recebi esse erro apenas porque o nome de um argumento do construtor diferia no arquivo de cabeçalho e no arquivo de implementação. A assinatura do construtor é

PointSet (const PointSet & pset, Parent * parent = 0);

e o que eu escrevi na implementação começou com

PointSet (const PointSet & pest, Parent * parent)

assim, acidentalmente substituí "pset" por "pest". O compilador estava reclamando deste e de outros dois construtores nos quais não havia nenhum erro. Estou usando o g ++ versão 4.9.1 no Ubuntu. E definir um destruidor virtual nessa classe derivada não fez diferença (é definido na classe base). Eu nunca teria encontrado esse bug se não colasse os corpos dos construtores no arquivo de cabeçalho, definindo-os na classe.

vitke
fonte
7
Isso não faria nenhuma diferença, você deve ter o erro em outro lugar e, inadvertidamente, corrigido.
MM