简体   繁体   中英

C++ - virtual destructors and linker errors

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;
};
  • Constructor: Once you declare a constructor, any constructor, the compiler doesn't generate a default constructor for you. The constructor of the derived class tries to call the interface's constructor and it is not defined, just declared. Either provide a definition, or remove the declaration
  • Destructor: Other considerations left alone (for example, similar considerations as above) your destructor is virtual. Every non-pure virtual function must have a definition (because it is by definition used ).

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM