我写了这个界面:
#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; }
答案 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.
}