C++ - 虚拟析构函数和链接器错误

Posted

技术标签:

【中文标题】C++ - 虚拟析构函数和链接器错误【英文标题】:C++ - virtual destructors and linker errors 【发布时间】:2011-12-22 21:23:03 【问题描述】:

我得到了我写的这个界面:

#ifndef _I_LOG_H
#define _I_LOG_H

class ILog 
public:
    ILog();
    virtual ~ILog();

    virtual void LogInfo(const char* msg, ...) = 0;
    virtual void LogDebug(const char* msg, ...) = 0;
    virtual void LogWarn(const char* msg, ...) = 0;
    virtual void LogError(const char* msg, ...) = 0;

private: 
    Monkey* monkey;
;

#endif

这些方法是纯虚的,因此必须通过派生类来实现。 如果我尝试创建一个继承此接口的类,则会收到以下链接器错误:

Undefined reference to ILog::ILog
Undefined reference to ILog::~ILog

我明白为什么会有一个虚拟析构函数(以确保调用派生的析构函数),但我不明白为什么会出现此链接器错误。

编辑:好的,所以我还需要定义虚拟析构函数。 但是我仍然可以在虚拟析构函数的定义中执行一些东西,还是会简单地调用我的派生类析构函数并跳过它? 喜欢,这会触发吗:

virtual ~ILog()  delete monkey; 

【问题讨论】:

What is an undefined reference/unresolved external symbol error and how do I fix it?的可能重复 【参考方案1】:

你还没有定义构造函数和析构函数,你只是声明了它们

试试

class ILog 
public:
    //note, I want the compiler-generated default constructor, so I don't write one
    virtual ~ILog() //empty body

    virtual void LogInfo(const char* msg, ...) = 0;
    virtual void LogDebug(const char* msg, ...) = 0;
    virtual void LogWarn(const char* msg, ...) = 0;
    virtual void LogError(const char* msg, ...) = 0;
;
构造函数:一旦您声明了构造函数,任何构造函数,编译器都不会为您生成默认构造函数。派生类的构造函数试图调用接口的构造函数,它没有被定义,只是被声明了。要么提供定义,要么删除声明 析构函数:其他考虑因素(例如,与上述类似的考虑因素)您的析构函数是虚拟的。每个非纯虚函数必须有一个定义(因为它根据定义使用)。

我还能在虚拟析构函数的定义中执行一些东西吗? 还是会简单地调用我的派生类析构函数并跳过它? 喜欢,这会触发吗

是的,你可以。当派生类的析构函数被调用时,会自动调用基类的析构函数。但是,我想不出在接口的析构函数中做些什么是有意义的。但从技术上讲,你可以在析构函数中做任何事情,即使它是虚拟的

【讨论】:

我可以在虚拟析构函数定义中做些什么吗?更新了我的问题 @KaiserJohaan:当然可以,但是如果 interface 只声明纯虚函数,你会怎么做?你会做哪些可能的清理工作?如果有的话,应该由派生类(接口的实现者)清理它们使用的资源。 @KaiserJohaan:更新了我的答案【参考方案2】:

您忘记为虚拟析构函数添加一个空函数。函数体实际上并没有做任何事情,C++ 可能会将低级破坏代码放在派生类的析构函数中(不完全确定),但它仍然是必需的:

#ifndef _I_LOG_H
#define _I_LOG_H

struct ILog 
    virtual ~ILog();
    // virtual ~ILog() = 0; // either works

    virtual void LogInfo(const char* msg, ...) = 0;
    virtual void LogDebug(const char* msg, ...) = 0;
    virtual void LogWarn(const char* msg, ...) = 0;
    virtual void LogError(const char* msg, ...) = 0;
;

#endif

CPP 文件:

ILog::~ILog()
 // this does get called

更新示例:

#include <iostream>

struct Monkey

    int data;
;

struct ILog

    ILog() : monkey(0) 
    virtual ~ILog() = 0;

    virtual void LogInfo(const char* msg, ...) = 0;
    virtual void LogDebug(const char* msg, ...) = 0;
    virtual void LogWarn(const char* msg, ...) = 0;
    virtual void LogError(const char* msg, ...) = 0;

    void storeMonkey(Monkey* pM)
    
        delete monkey;
        monkey = pM;
    

    void message()
    
        std::cout << "monkey->data contains " << monkey->data;
    

private:
    Monkey* monkey;
;

struct ILogD : ILog

    int data;

    ILogD(Monkey* pM)
    
        storeMonkey(pM);
    

    void LogInfo(const char* msg, ...) ;
    void LogDebug(const char* msg, ...) ;
    void LogWarn(const char* msg, ...) ;
    void LogError(const char* msg, ...) ;
;

ILog::~ILog()

    delete monkey;




int main()

    ILogD o(new Monkey());

    o.message();

【讨论】:

"您忘记为纯虚析构函数添加一个空函数。"这里的析构函数不是 pure 虚拟的。它不应该是 语法是纯虚拟的,尽管从技术上讲它不是析构函数。 我不明白你在说什么对不起。我要说的是,在最初的问题中,析构函数只是虚拟的,而不是 pure virtual 我的版本有效,他的无效。但是,如果您要我删除我的答案,我会非常高兴。只是投了几次票,所以我可以得到徽章。大声笑。 您的解决方案工作得很好,我没有说它没有。我只想说您写道,OP 的 dtor 是纯虚拟的,但事实并非如此。我只是想让你改进你的答案。我无意投反对票。这是一个很好的答案。但是,当它包含不正确的信息时,我也不能投票【参考方案3】:

只需提供构造函数和析构函数的内联版本,编译器不会生成对它们的引用以使链接器失败。

ILog() ;
virtual ~ILog() ;

【讨论】:

【参考方案4】:

一切都没有丢失!除了在模板类的情况下,纯虚析构函数被调用。 C++ 并没有真正的接口,但纯抽象类的工作方式与所有虚函数都设置为 0 的方式相同,从而创建一个空的虚函数表。由于不同编译器实现的复杂性和差异,大多数 C++ 程序员避免使用纯抽象类之外的任何东西的多重继承。 您的类不是纯虚拟类,因为您有成员数据,并且您需要一个不是纯虚函数的析构函数来清理它。不用担心有解决方法!

您的结构类需要如下所示:

#ifndef _I_LOG_H 
#define _I_LOG_H
 
 struct ILog 
     virtual ~ILog() = 0;  // JDM: This is how you make it abstract
     virtual void LogInfo(const char* msg, ...) = 0;
     virtual void LogDebug(const char* msg, ...) = 0;
     virtual void LogWarn(const char* msg, ...) = 0;
     virtual void LogError(const char* msg, ...) = 0;
 ;
 #endif

现在执行此操作的正确方法是在您的 ILog.cpp 中:

#include "Ilog.h"
// only for the dtor
ILog::~ILog()
    // code here will get called!

我确实提到了一些关于模板的内容,这超出了您的问题范围,但理解起来很重要。必须为模板类实现专门的纯虚析构函数:

 #ifndef _I_LOG_H 
 #define _I_LOG_H
 
 template<class T> class ILog 
     virtual ~ILog() = 0;  // JDM: This is how you make it abstract
     virtual void LogInfo(T msg, ...) = 0;
     virtual void LogDebug(T msg, ...) = 0;
     virtual void LogWarn(T msg, ...) = 0;
     virtual void LogError(T msg, ...) = 0;
 ;
 #endif

假设我有:

class LogMsg 
    const char* message;
    LogMsg(const char * const msg) 
        message = msg;
     
    // More Stuff

然后我将你的类与 LogMsg 一起使用:

#include "ILog.h"
class Log : ILog<LogMsg> 
   // implement ILog...
   virtual ~Log();
 

在我的 CPP 中:

#include "Log.h"
Log::~Log() 
   // this gets called

// Link error without the following
template<class LogMsg> ILog<LogMsg>::~ILog 
   // This gets called.

【讨论】:

以上是关于C++ - 虚拟析构函数和链接器错误的主要内容,如果未能解决你的问题,请参考以下文章

C++ 虚拟析构函数 (virtual destructor)

C++中的继承和纯虚函数

为啥纯虚析构函数需要实现

为啥我们需要 C++ 中的纯虚析构函数?

C++ 中的虚拟默认析构函数

没有虚拟构造函数,只有虚拟析构函数