【问题标题】:Why must I implement concrete destructor on abstract class? [duplicate]为什么我必须在抽象类上实现具体的析构函数? [复制]
【发布时间】:2014-08-05 17:59:01
【问题描述】:

我是 C++11 的新手,我正在尝试构建一个经典的接口/实现对。考虑以下示例:

#include <iostream>
#include <memory>

// Interface
class IFace {
public:
    virtual ~IFace () = 0;
};
// Why must I define this concrete implementation?
inline IFace::~IFace () { }

// Implementation
class Impl : public IFace {
public:
    virtual ~Impl () { }
};

int main (int argc, char ** argv) {
    auto impl = std::shared_ptr<Impl> (new Impl ());
    return 0;
}

如果我在接口 inline IFace::~IFace () { } 上注释掉不需要的具体析构函数,我会收到链接错误

Undefined symbols for architecture x86_64:
  "IFace::~IFace()", referenced from:
      Impl::~Impl() in ifac_impl.cpp.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

即使接口和实现是模板化的,我也必须这样做:

// Template IFace2
template <typename T>
class IFace2 {
public:
    virtual ~IFace2 () = 0;
};
// I have to do this even if the pair is templated.
template <typename T>
inline IFace2<T>::~IFace2<T> () { }

template <typename T>
class Impl2 : public IFace2<T> {
public:
    virtual ~Impl2 () { }
};

int main (int argc, char ** argv) {
    auto impl = std::shared_ptr<Impl> (new Impl ());
    auto impl2 = std::shared_ptr<Impl2<double> > (new Impl2<double> ());
    return 0;
}

为什么?

第二个问题是“我是不是走错路了?”也就是说,对于我想做的事情,是否有更好的模式(成语?)?诚然,我试图将我用 C# 开发的概念模式融入 C++11。这是理智的吗?如果不是,那么理智的方法是什么?

【问题讨论】:

标签: c++ c++11 interface


【解决方案1】:

当你在派生类中重写一个虚函数,然后调用它时,基本实现不会被调用,除非你显式调用它。

例如,

struct base
{
  virtual void foo() {}
};

struct derived : base
{
  void foo() override {}
};

base *b = new derived;
b->foo();  // this will not call base::foo

这同样适用于纯虚函数。

但是,在(纯)虚拟析构函数的情况下,基析构函数将被调用(因为基子对象需要被销毁)。因此,如果你有一个纯虚析构函数,你必须提供一个实现。否则你将永远无法实例化任何派生类。

【讨论】:

    【解决方案2】:

    析构函数不应该是纯虚函数。这将使类 abstract 并因此无法实例化。只需将其定义为default

    class IFace {
    public:
        virtual ~IFace () = default;
    };
    

    Live demo

    【讨论】:

    • 但我希望接口类是抽象的。我希望(大部分)使用这些类的代码仅引用接口。唯一应该“看到”实现的代码是调用构造函数的代码。其余代码应仅通过接口访问实现。我希望这是为了可测试性和模拟目的:在各种场景中,应该“注入”具体实现的模拟和模拟来代替其他具体实现。这是我用 C# 开发的模式——抽象和具体的严格分离。这在 C++11 中不理智吗?
    • @Reb.Cabin 然后你需要找到至少一个可以是纯虚拟的函数(并且对它有意义)。如果你没有,那么你可能不应该使用继承。
    • @Jeffrey 是的,在我的实际代码中,我有很多这样的纯虚函数。我减少了它们以使我的问题尽可能小。
    • @Reb.Cabin 一个类是抽象,只要它至少有一个纯虚函数。所以你可以让你的析构函数是虚拟的但不是纯的,让你的其他接口函数是纯的,你仍然有一个有效的接口模式。
    • @Matt 谢谢,这对我来说似乎很清楚。
    猜你喜欢
    • 1970-01-01
    • 2011-10-02
    • 1970-01-01
    • 2015-02-28
    • 2022-01-17
    • 1970-01-01
    • 2011-01-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多