4

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; }
KaiserJohaan
  • 9,028
  • 20
  • 112
  • 199
  • possible duplicate of [What is an undefined reference/unresolved external symbol error and how do I fix it?](http://stackoverflow.com/questions/12573816/what-is-an-undefined-reference-unresolved-external-symbol-error-and-how-do-i-fix) – Akhil V Suku Aug 12 '15 at 06:42

4 Answers4

9

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

Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434
  • Can I do stuff in the virtuals destructor definition? updated my question – KaiserJohaan Dec 22 '11 at 21:49
  • @KaiserJohaan: Sure you can, but what would you do in case of an ***interface*** that only declares pure virtual functions? What possible cleanup would you do? It's the derived classes (implementers of the interface) should that clean up the resources they use, if any. – Armen Tsirunyan Dec 22 '11 at 21:51
3

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();
}
Truncheon
  • 916
  • 2
  • 12
  • 25
  • "You forgot to add an empty function for the pure virtual destructor." The destructor here is not ***pure*** virtual. And it shouldn't be – Armen Tsirunyan Dec 22 '11 at 21:50
  • The syntax is pure virtual, even though the destructor it isn't technically. – Truncheon Dec 22 '11 at 21:51
  • I don't see what you're saying sorry. What I am saying is that in the original question the destructor is just virtual, not ***pure*** virtual – Armen Tsirunyan Dec 22 '11 at 21:52
  • My version works, his doesn't. But if you want me to delete my answer, I will be more than happy. Just down vote a few times, so I can get the badge. Lol. – Truncheon Dec 22 '11 at 21:56
  • Your solution works just fine, I haven't said it doesn't. I just want to say that you wrote that the OP's dtor was pure virtual which it wasn't. I just wanted you to improve your answer. I have no intentions to downvote it. It's a nice answer. But neither can I upvote it while it contains incorrect information – Armen Tsirunyan Dec 22 '11 at 21:59
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/6071/discussion-between-truncheon-and-armen-tsirunyan) – Truncheon Dec 22 '11 at 22:20
0

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.
}
Jack D Menendez
  • 154
  • 1
  • 7
0

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() {};
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622