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

时间:2011-12-22 21:23:03

标签: c++ inheritance linker abstract-class

我写了这个界面:

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

4 个答案:

答案 0 :(得分:10)

您尚未定义构造函数和析构函数,您只声明了它们

尝试

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;
};
  • 构造函数:一旦声明了构造函数,任何构造函数,编译器就不会为您生成默认构造函数。派生类的构造函数尝试调用接口的构造函数,并且未定义,只是声明。提供定义或删除声明
  • 析构函数:其他考虑因素(例如,上述类似的考虑因素)您的析构函数是虚拟的。每个非纯虚函数 必须 都有一个定义(因为它按照定义使用)。
  

我还可以在虚拟析构函数的定义中执行某些操作,   或者它只是调用我的派生类析构函数并跳过它?   比如,这会触发

是的,你可以。当调用派生类的析构函数时,它将自动调用基类的析构函数。然而,我无法想到在接口的析构函数中进行操作是有意义的。但从技术上讲,你可以在析构函数中做任何事情,即使它是虚拟的

答案 1 :(得分:3)

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

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

一切都没有丢失!除了模板类的情况外,都会调用纯虚拟析构函数。 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.
}