I got this interface I've written:
#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
The methods are pure virtual and therefore must be implemented by deriving classes. If I try to make a class that inherits this interface I get the following linker errors:
Undefined reference to ILog::ILog
Undefined reference to ILog::~ILog
I understand why there is a virtual destructor (to make sure the derived's destructor is called) but I do not understand why I get this linker error.
EDIT: Okay, so I need to define the virtual destructor as well. But can I still perform stuff in the definition of the virtual destructor, or will it simply call my derived classes destructor and skip it? Like, will this trigger:
virtual ~ILog() { delete monkey; }
You haven't defined the constructor and destructor, you have only declared them
Try
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;
};
can I still perform stuff in the definition of the virtual destructor, or will it simply call my derived classes destructor and skip it? Like, will this trigger
Yes you can. When the destructor of the derived class is called, it will automatically call the base class's destructor. However there isn't much I can think of that it would make sense to do in a destructor of an interface. But technically you can do anything in the destructor, even if it is virtual
You forgot to add an empty function for the virtual destructor. The function body doesn't actually do anything, and C++ might put the low-level destruction code in the derived class destructor (not totally sure on that), but it's still required:
#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 file:
ILog::~ILog()
{ // this does get called
}
Updated example:
#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();
}
All is not lost! Pure virtual destructors are called except in the case of template classes. C++ doesn't really have interfaces but pure abstract classes work the same way where all virtual functions are set = 0 creating an empty virtual function table. Most C++ programmers eschew multiple inheritance of anything beyond pure abstract classes because of complications and differences in implementation by different compilers. Your class is not a pure virtual class as you have member data and you need a destructor that is not a pure virtual function to clean it up. Fear not there is a workaround!
Your struct class needs to look like this:
#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
Now the proper way to do this is in your ILog.cpp:
#include "Ilog.h"
// only for the dtor
ILog::~ILog(){
// code here will get called!
}
I did mention something about templates which is beyond the scope of your question but is important to understand. One has to implement the specialized pure virtual destructor for a template class:
#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
Suppose I have:
class LogMsg {
const char* message;
LogMsg(const char * const msg) {
message = msg;
}
// More Stuff
}
Then I use your class like this with LogMsg:
#include "ILog.h"
class Log : ILog<LogMsg> {
// implement ILog...
virtual ~Log();
}
And in my CPP:
#include "Log.h"
Log::~Log() {
// this gets called
}
// Link error without the following
template<class LogMsg> ILog<LogMsg>::~ILog {
// This gets called.
}
Just provide an inline version of the constructor and destructor and the compiler won't generate a reference to them for the linker to fail on.
ILog() {};
virtual ~ILog() {};
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.