【问题标题】:C++ - virtual destructors and linker errorsC++ - 虚拟析构函数和链接器错误
【发布时间】:2011-12-22 21:23:03
【问题描述】:

我得到了我写的这个界面:

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

【问题讨论】:

标签: c++ inheritance linker abstract-class


【解决方案1】:

你还没有定义构造函数和析构函数,你只是声明了它们

试试

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

我还能在虚拟析构函数的定义中执行一些东西吗? 还是会简单地调用我的派生类析构函数并跳过它? 喜欢,这会触发吗

是的,你可以。当派生类的析构函数被调用时,会自动调用基类的析构函数。但是,我想不出在接口的析构函数中做些什么是有意义的。但从技术上讲,你可以在析构函数中做任何事情,即使它是虚拟的

【讨论】:

  • 我可以在虚拟析构函数定义中做些什么吗?更新了我的问题
  • @KaiserJohaan:当然可以,但是如果 interface 只声明纯虚函数,你会怎么做?你会做哪些可能的清理工作?如果有的话,应该由派生类(接口的实现者)清理它们使用的资源。
  • @KaiserJohaan:更新了我的答案
【解决方案2】:

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

【讨论】:

  • "您忘记为纯虚析构函数添加一个空函数。"这里的析构函数不是 pure 虚拟的。它不应该是
  • 语法是纯虚拟的,尽管从技术上讲它不是析构函数。
  • 我不明白你在说什么对不起。我要说的是,在最初的问题中,析构函数只是虚拟的,而不是 pure virtual
  • 我的版本有效,他的无效。但是,如果您要我删除我的答案,我会非常高兴。只是投了几次票,所以我可以得到徽章。大声笑。
  • 您的解决方案工作得很好,我没有说它没有。我只想说您写道,OP 的 dtor 是纯虚拟的,但事实并非如此。我只是想让你改进你的答案。我无意投反对票。这是一个很好的答案。但是,当它包含不正确的信息时,我也不能投票
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 2011-02-03
      • 1970-01-01
      • 2017-05-31
      • 2017-05-01
      • 2011-08-12
      • 1970-01-01
      • 2012-10-24
      • 2018-11-26
      • 2012-04-18
      相关资源
      最近更新 更多