虚析构函数和链接器错误

C++ - virtual destructors and linker errors

本文关键字:错误 链接 析构函数      更新时间:2023-10-16

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

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

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

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

  • Constructor:一旦你声明了一个构造函数,任何构造函数,编译器都不会为你生成默认的构造函数。派生类的构造函数尝试调用接口的构造函数,它没有定义,只是声明。要么提供定义,要么删除
  • 声明
  • Destructor:其他注意事项(例如,与上面类似的注意事项)您的析构函数是虚拟的。每个非纯虚函数必须有一个定义(因为根据定义使用)。

仍然可以执行虚析构函数定义中的内容吗?还是简单地调用派生类的析构函数并跳过它?比如,这会触发

吗?

是的,你可以。当调用派生类的析构函数时,它将自动调用基类的析构函数。然而,我认为在接口的析构函数中做这样的事情没有什么意义。但从技术上讲,你可以在析构函数中做任何事情,即使它是虚的

您忘记为虚析构函数添加一个空函数。函数体实际上不做任何事情,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();
}

只要提供构造函数和析构函数的内联版本,编译器就不会生成对它们的引用,从而导致链接器失败。

ILog() {};
virtual ~ILog() {};

并非全输了!调用纯虚析构函数,模板类除外。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.
}
相关文章: