为什么我们需要 C++ 中的纯虚拟析构函数?

发布于 2024-07-29 16:57:51 字数 300 浏览 5 评论 0原文

我了解虚拟析构函数的需要。 但为什么我们需要一个虚拟析构函数? 在一篇 C++ 文章中,作者提到当我们想要使类抽象时,我们使用纯虚析构函数。

但是我们可以通过将任何成员函数设置为纯虚函数来使类抽象。

所以我的问题是

  1. 我们什么时候才能真正使析构函数成为纯虚拟的? 有人能给出一个很好的实时例子吗?

  2. 当我们创建抽象类时,将析构函数设置为纯虚函数是一个好习惯吗? 如果是的话..那为什么?

I understand the need for a virtual destructor. But why do we need a pure virtual destructor? In one of the C++ articles, the author has mentioned that we use pure virtual destructor when we want to make a class abstract.

But we can make a class abstract by making any of the member functions as pure virtual.

So my questions are

  1. When do we really make a destructor pure virtual? Can anybody give a good real time example?

  2. When we are creating abstract classes is it a good practice to make the destructor also pure virtual? If yes..then why?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

(り薆情海 2024-08-05 16:57:51
  1. 允许纯虚拟析构函数的真正原因可能是禁止它们意味着向语言添加另一条规则,并且不需要此规则,因为允许纯虚拟析构函数不会产生任何不良影响。

  2. 不,普通的旧虚拟就足够了。

如果您创建一个具有虚拟方法默认实现的对象,并且希望使其抽象而不强迫任何人重写任何特定方法,则可以将析构函数设为纯虚拟。 我不认为这有什么意义,但这是可能的。

请注意,由于编译器将为派生类生成隐式析构函数,因此如果类的作者不这样做,则任何派生类都将不是抽象的。 因此,在基类中拥有纯虚拟析构函数不会对派生类产生任何影响。 它只会使基类抽象(感谢 @kappa 的评论)。

人们还可能假设每个派生类可能需要具有特定的清理代码,并使用纯虚拟析构函数作为编写代码的提醒,但这似乎是人为的(并且是非强制的)。

注意:析构函数是唯一的方法,即使它纯虚拟必须有一个实现,以便实例化派生类(是的纯虚函数可以有实现,纯虚意味着派生类必须重写此方法,这与实现是正交的)。

struct foo {
    virtual void bar() = 0;
};

void foo::bar() { /* default implementation */ }

class foof : public foo {
    void bar() { foo::bar(); } // have to explicitly call default implementation.
};
  1. Probably the real reason that pure virtual destructors are allowed is that to prohibit them would mean adding another rule to the language and there's no need for this rule since no ill-effects can come from allowing a pure virtual destructor.

  2. Nope, plain old virtual is enough.

If you create an object with default implementations for its virtual methods and want to make it abstract without forcing anyone to override any specific method, you can make the destructor pure virtual. I don't see much point in it but it's possible.

Note that since the compiler will generate an implicit destructor for derived classes, if the class's author does not do so, any derived classes will not be abstract. Therefore having the pure virtual destructor in the base class will not make any difference for the derived classes. It will only make the base class abstract (thanks for @kappa's comment).

One may also assume that every deriving class would probably need to have specific clean-up code and use the pure virtual destructor as a reminder to write one but this seems contrived (and unenforced).

Note: The destructor is the only method that even if it is pure virtual has to have an implementation in order to instantiate derived classes (yes pure virtual functions can have implementations, being pure virtual means derived classes must override this method, this is orthogonal to having an implementation).

struct foo {
    virtual void bar() = 0;
};

void foo::bar() { /* default implementation */ }

class foof : public foo {
    void bar() { foo::bar(); } // have to explicitly call default implementation.
};
阳光的暖冬 2024-08-05 16:57:51

对于一个抽象类来说,你所需要的只是至少一个纯虚函数。 任何函数都可以; 但事实上,析构函数是任何类都会有的东西,所以它总是作为候选者存在。 此外,使析构函数成为纯虚拟的(而不是仅仅虚拟的)除了使类抽象之外没有任何行为副作用。 因此,许多风格指南建议一致地使用纯虚拟析构函数来指示类是抽象的——如果没有其他原因,它提供了一个一致的位置,让阅读代码的人可以查看该类是否是抽象的。

All you need for an abstract class is at least one pure virtual function. Any function will do; but as it happens, the destructor is something that any class will have—so it's always there as a candidate. Furthermore, making the destructor pure virtual (as opposed to just virtual) has no behavioral side effects other than to make the class abstract. As such, a lot of style guides recommend that the pure virtual destuctor be used consistently to indicate that a class is abstract—if for no other reason than it provides a consistent place someone reading the code can look to see if the class is abstract.

惯饮孤独 2024-08-05 16:57:51

如果您想创建一个抽象基类:

  • 无法实例化(是的,这对于术语“抽象”来说是多余的!)
  • 但是需要虚拟析构函数行为(您打算携带指向 ABC 的指针而不是指向派生类型的指针,并通过它们进行删除),
  • 但其他方法不需要任何其他虚拟调度行为(也许有有 没有其他方法吗?考虑一个简单的受保护的“资源”容器,它需要构造函数/析构函数/赋值,但没有太多其他方法)

...最简单的方法是通过使析构函数成为纯虚拟函数来使类抽象 为其提供定义(方法体)。

对于我们假设的 ABC:

您保证它不能被实例化(即使是在类本身内部,这就是为什么私有构造函数可能还不够),您可以获得析构函数所需的虚拟行为,并且您不必查找和将另一个不需要虚拟调度的方法标记为“虚拟”。

If you want to create an abstract base class:

  • that can't be instantiated (yep, this is redundant with the term "abstract"!)
  • but needs virtual destructor behavior (you intend to carry around pointers to the ABC rather than pointers to the derived types, and delete through them)
  • but does not need any other virtual dispatch behavior for other methods (maybe there are no other methods? consider a simple protected "resource" container that needs a constructors/destructor/assignment but not much else)

...it's easiest to make the class abstract by making the destructor pure virtual and providing a definition (method body) for it.

For our hypothetical ABC:

You guarantee that it cannot be instantiated (even internal to the class itself, this is why private constructors may not be enough), you get the virtual behavior you want for the destructor, and you do not have to find and tag another method that doesn't need virtual dispatch as "virtual".

满意归宿 2024-08-05 16:57:51

这里我想告诉一下我们什么时候需要虚析构函数,什么时候需要纯虚析构函数

class Base
{
public:
    Base();
    virtual ~Base() = 0; // Pure virtual, now no one can create the Base Object directly 
};

Base::Base() { cout << "Base Constructor" << endl; }
Base::~Base() { cout << "Base Destructor" << endl; }


class Derived : public Base
{
public:
    Derived();
    ~Derived();
};

Derived::Derived() { cout << "Derived Constructor" << endl; }
Derived::~Derived() {   cout << "Derived Destructor" << endl; }


int _tmain(int argc, _TCHAR* argv[])
{
    Base* pBase = new Derived();
    delete pBase;

    Base* pBase2 = new Base(); // Error 1   error C2259: 'Base' : cannot instantiate abstract class
}
  1. 当你希望没有人能够创建Base类的对象时直接使用纯虚拟析构函数virtual ~Base() = 0。 通常至少需要一个纯虚函数,让我们以virtual ~Base() = 0作为这个函数。

  2. 当你不需要上面的东西时,只需要派生类对象的安全销毁

    Base* pBase = new Derived();
    删除pBase;
    不需要纯虚析构函数,只有虚析构函数就可以完成这项工作。

Here I want to tell when we need virtual destructor and when we need pure virtual destructor

class Base
{
public:
    Base();
    virtual ~Base() = 0; // Pure virtual, now no one can create the Base Object directly 
};

Base::Base() { cout << "Base Constructor" << endl; }
Base::~Base() { cout << "Base Destructor" << endl; }


class Derived : public Base
{
public:
    Derived();
    ~Derived();
};

Derived::Derived() { cout << "Derived Constructor" << endl; }
Derived::~Derived() {   cout << "Derived Destructor" << endl; }


int _tmain(int argc, _TCHAR* argv[])
{
    Base* pBase = new Derived();
    delete pBase;

    Base* pBase2 = new Base(); // Error 1   error C2259: 'Base' : cannot instantiate abstract class
}
  1. When you want that no one should be able to create the object of Base class directly, use pure virtual destructor virtual ~Base() = 0. Usually at-least one pure virtual function is required, let's take virtual ~Base() = 0, as this function.

  2. When you do not need above thing, only you need the safe destruction of Derived class object

    Base* pBase = new Derived();
    delete pBase;
    pure virtual destructor is not required, only virtual destructor will do the job.

芯好空 2024-08-05 16:57:51

从我读到的你的问题的答案中,我无法推断出实际使用纯虚拟析构函数的充分理由。 例如,下面的理由根本不能说服我:

允许纯虚拟析构函数的真正原因可能是禁止它们意味着向语言添加另一条规则,并且不需要此规则,因为允许纯虚拟析构函数不会产生任何不良影响。

在我看来,纯虚拟析构函数可能很有用。 例如,假设您的代码中有两个类 myClassA 和 myClassB,并且 myClassB 继承自 myClassA。 由于 Scott Meyers 在他的书“更有效的 C++”第 33 项“使非叶类抽象”中提到的原因,更好的做法是实际创建一个抽象类 myAbstractClass,myClassA 和 myClassB 从中继承。 这提供了更好的抽象并防止因对象副本等问题而出现的一些问题。

在抽象过程(创建类 myAbstractClass)中,myClassA 或 myClassB 中的任何方法都可能不是纯虚方法的良好候选者(这是 myAbstractClass 抽象的先决条件)。 在这种情况下,您将抽象类的析构函数定义为纯虚函数。

下面是我自己编写的一些代码的具体示例。 我有两个类,Numerics/PhysicsParams,它们共享共同的属性。 因此我让它们继承抽象类IParams。 在这种情况下,我手头绝对没有可以纯粹虚拟的方法。 例如,每个子类的 setParameter 方法必须具有相同的主体。 我唯一的选择就是使 IParams 的析构函数成为纯虚拟的。

struct IParams
{
    IParams(const ModelConfiguration& aModelConf);
    virtual ~IParams() = 0;

    void setParameter(const N_Configuration::Parameter& aParam);

    std::map<std::string, std::string> m_Parameters;
};

struct NumericsParams : IParams
{
    NumericsParams(const ModelConfiguration& aNumericsConf);
    virtual ~NumericsParams();

    double dt() const;
    double ti() const;
    double tf() const;
};

struct PhysicsParams : IParams
{
    PhysicsParams(const N_Configuration::ModelConfiguration& aPhysicsConf);
    virtual ~PhysicsParams();

    double g()     const; 
    double rho_i() const; 
    double rho_w() const; 
};

From the answers I have read to your question, I couldn't deduce a good reason to actually use a pure virtual destructor. For example, the following reason doesn't convince me at all:

Probably the real reason that pure virtual destructors are allowed is that to prohibit them would mean adding another rule to the language and there's no need for this rule since no ill-effects can come from allowing a pure virtual destructor.

In my opinion, pure virtual destructors can be useful. For example, assume you have two classes myClassA and myClassB in your code, and that myClassB inherits from myClassA. For the reasons mentioned by Scott Meyers in his book "More Effective C++", Item 33 "Making non-leaf classes abstract", it is better practice to actually create an abstract class myAbstractClass from which myClassA and myClassB inherit. This provides better abstraction and prevents some problems arising with, for example, object copies.

In the abstraction process (of creating class myAbstractClass), it can be that no method of myClassA or myClassB is a good candidate for being a pure virtual method (which is a prerequisite for myAbstractClass to be abstract). In this case, you define the abstract class's destructor pure virtual.

Hereafter a concrete example from some code I have myself written. I have two classes, Numerics/PhysicsParams which share common properties. I therefore let them inherit from the abstract class IParams. In this case, I had absolutely no method at hand that could be purely virtual. The setParameter method, for example, must have the same body for every subclass. The only choice that I have had was to make IParams' destructor pure virtual.

struct IParams
{
    IParams(const ModelConfiguration& aModelConf);
    virtual ~IParams() = 0;

    void setParameter(const N_Configuration::Parameter& aParam);

    std::map<std::string, std::string> m_Parameters;
};

struct NumericsParams : IParams
{
    NumericsParams(const ModelConfiguration& aNumericsConf);
    virtual ~NumericsParams();

    double dt() const;
    double ti() const;
    double tf() const;
};

struct PhysicsParams : IParams
{
    PhysicsParams(const N_Configuration::ModelConfiguration& aPhysicsConf);
    virtual ~PhysicsParams();

    double g()     const; 
    double rho_i() const; 
    double rho_w() const; 
};
夢归不見 2024-08-05 16:57:51

如果要停止基类的实例化而不对已实现和测试的派生类进行任何更改,则可以在基类中实现纯虚拟析构函数。

If you want to stop instantiating of base class without making any change in your already implemented and tested derive class, you implement a pure virtual destructor in your base class.

相思碎 2024-08-05 16:57:51

你正在对这些答案进行假设,所以为了清楚起见,我将尝试做出一个更简单、更实际的解释。

面向对象设计的基本关系有两个:
IS-A 和 HAS-A。 这些不是我编造的。 他们就是这么称呼的。

IS-A 指示特定对象标识为类层次结构中位于其之上的类。 如果香蕉对象是水果类的子类,那么它就是水果对象。 这意味着只要可以使用水果类,就可以使用香蕉。 但它不是反射性的。 如果需要特定类,则不能用基类替换该特定类。

has-a 表示对象是复合类的一部分,并且存在所有权关系。 这在 C++ 中意味着它是一个成员对象,因此拥有它的类有责任在销毁它自己之前处理它或移交所有权。

这两个概念在单继承语言中比在 C++ 等多继承模型中更容易实现,但规则本质上是相同的。 当类标识不明确时,例如将 Banana 类指针传递到采用 Fruit 类指针的函数中,情况就会变得复杂。

虚函数首先是一个运行时的东西。 它是多态性的一部分,因为它用于在运行的程序中调用该函数时决定运行哪个函数。

virtual 关键字是一个编译器指令,用于在类标识存在歧义时按特定顺序绑定函数。 虚函数总是在父类中(据我所知),并向编译器指示成员函数与其名称的绑定应该首先是子类函数,然后是父类函数。

Fruit 类可以有一个默认返回“NONE”的虚拟函数 color()。
Banana 类 color() 函数返回“YELLOW”或“BROWN”。

但是,如果采用 Fruit 指针的函数调用发送给它的 Banana 类上的 color() ,那么会调用哪个 color() 函数?
该函数通常会为 Fruit 对象调用 Fruit::color() 。

99% 的情况下这都不是我们想要的。
但是,如果 Fruit::color() 被声明为 virtual,则将为该对象调用 Banana:color(),因为正确的 color() 函数将在调用时绑定到 Fruit 指针。
运行时将检查指针指向哪个对象,因为它在 Fruit 类定义中被标记为虚拟。

这与重写子类中的函数不同。 在这种情况下
如果 Fruit 指针只知道它是指向 Fruit 的指针,则它会调用 Fruit::color()。

现在出现了“纯虚函数”的想法。
这是一个相当不幸的短语,因为纯洁与它无关。 这意味着永远不会调用基类方法。
确实不能调用纯虚函数。 然而,它仍然必须被定义。 函数签名必须存在。 许多编码员为了完整性而制作了一个空的实现 {},但如果没有,编译器将在内部生成一个。 在这种情况下,当调用该函数时,即使指针指向 Fruit ,也会调用 Banana::color() ,因为它是 color() 的唯一实现。

现在拼图的最后一块:构造函数和析构函数。

纯虚拟构造函数是完全非法的。 那是刚刚出来的。

但是纯虚拟析构函数在您想要禁止创建基类实例的情况下确实有效。 如果基类的析构函数是纯虚函数,则只能实例化子类。
约定是将其分配给 0。

 virtual ~Fruit() = 0;  // pure virtual 
 Fruit::~Fruit(){}      // destructor implementation

在这种情况下,您必须创建一个实现。 编译器知道这就是您正在做的事情,并确保您做得正确,否则它会强烈抱怨它无法链接到它需要编译的所有函数。 如果您在如何建模类层次结构方面没有走上正确的道路,这些错误可能会令人困惑。

因此,在这种情况下,您被禁止创建 Fruit 实例,但允许创建 Banana 实例。

调用删除指向 Banana 实例的 Fruit 指针
总是先调用 Banana::~Banana() 然后调用 Fuit::~Fruit() 。
因为无论怎样,当你调用子类析构函数时,必须跟随基类析构函数。

这是一个糟糕的模型吗? 是的,它在设计阶段更复杂,但它可以确保在运行时执行正确的链接,并且在访问哪个子类存在歧义的情况下执行子类函数。

如果您编写 C++ 时只传递精确的类指针,而不传递通用或模糊的指针,那么实际上并不需要虚函数。
但是,如果您需要类型的运行时灵活性(如 Apple Banana Orange ==> Fruit ),则函数会变得更简单、更通用,并且冗余代码更少。
您不再需要为每种类型的水果编写一个函数,并且您知道每种水果都会用其自己的正确函数响应 color() 。

我希望这个冗长的解释能够巩固这个概念而不是混淆事物。 有很多很好的例子可供参考,
观察足够多的内容,实际运行它们,搞乱它们,你就会得到它。

You are getting into hypotheticals with these answers, so I will try to make a simpler, more down to earth explanation for clarity's sake.

The basic relationships of object oriented design are two:
IS-A and HAS-A. I did not make those up. That is what they are called.

IS-A indicates that a particular object identifies as being of the class that is above it in a class hierarchy. A banana object is a fruit object if it is a subclass of the fruit class. This means that anywhere a fruit class can be used, a banana can be used. It is not reflexive , though. You can not substitute a base class for a specific class if that specific class is called for.

Has-a indicated that an object is part of a composite class and that there is an ownership relationship. It means in C++ that it is a member object and as such the onus is on the owning class to dispose of it or hand ownership off before destructing itself.

These two concepts are easier to realize in single-inheritance languages than in a multiple inheritance model like c++, but the rules are essentially the same. The complication comes when the class identity is ambiguous, such as passing a Banana class pointer into a function that takes a Fruit class pointer.

Virtual functions are, firstly, a run-time thing. It is part of polymorphism in that it is used to decide which function to run at the time it is called in the running program.

The virtual keyword is a compiler directive to bind functions in a certain order if there is ambiguity about the class identity. Virtual functions are always in parent classes (as far as I know) and indicate to the compiler that binding of member functions to their names should take place with the subclass function first and the parent class function after.

A Fruit class could have a virtual function color() that returns "NONE" by default.
The Banana class color() function returns "YELLOW" or "BROWN".

But if the function taking a Fruit pointer calls color() on the Banana class sent to it -- which color() function gets invoked?
The function would normally call Fruit::color() for a Fruit object.

That would 99% of the time not be what was intended.
But if Fruit::color() was declared virtual then Banana:color() would be called for the object because the correct color() function would be bound to the Fruit pointer at the time of the call.
The runtime will check what object the pointer points to because it was marked virtual in the Fruit class definition.

This is different than overriding a function in a subclass. In that case
the Fruit pointer will call Fruit::color() if all it knows is that it IS-A pointer to Fruit.

So now to the idea of a "pure virtual function" comes up.
It is a rather unfortunate phrase as purity has nothing to do with it. It means that it is intended that the base class method is never to be called.
Indeed a pure virtual function can not be called. It must still be defined, however. A function signature must exist. Many coders make an empty implementation {} for completeness, but the compiler will generate one internally if not. In that case when the function is called even if the pointer is to Fruit , Banana::color() will be called as it is the only implementation of color() there is.

Now the final piece of the puzzle: constructors and destructors.

Pure virtual constructors are illegal, completely. That is just out.

But pure virtual destructors do work in the case that you want to forbid the creation of a base class instance. Only sub classes can be instantiated if the destructor of the base class is pure virtual.
the convention is to assign it to 0.

 virtual ~Fruit() = 0;  // pure virtual 
 Fruit::~Fruit(){}      // destructor implementation

You do have to create an implementation in this case. The compiler knows this is what you are doing and makes sure you do it right, or it complains mightily that it can not link to all the functions it needs to compile. The errors can be confusing if you are not on the right track as to how you are modeling your class hierarchy.

So you are forbidden in this case to create instances of Fruit, but allowed to create instances of Banana.

A call to delete of the Fruit pointer that points to an instance of Banana
will call Banana::~Banana() first and then call Fuit::~Fruit(), always.
Because no matter what, when you call a subclass destructor, the base class destructor must follow.

Is it a bad model? It is more complicated in the design phase, yes, but it can ensure that correct linking is performed at run-time and that a subclass function is performed where there is ambiguity as to exactly which subclass is being accessed.

If you write C++ so that you only pass around exact class pointers with no generic nor ambiguous pointers, then virtual functions are not really needed.
But if you require run-time flexibility of types (as in Apple Banana Orange ==> Fruit ) functions become easier and more versatile with less redundant code.
You no longer have to write a function for each type of fruit, and you know that every fruit will respond to color() with its own correct function.

I hope this long-winded explanation solidifies the concept rather than confuses things. There are a lot of good examples out there to look at,
and look at enough and actually run them and mess with them and you will get it.

生生漫 2024-08-05 16:57:51

您要求提供一个示例,我相信以下内容提供了纯虚拟析构函数的原因。 我期待回复这是否是一个好的原因...

我不希望任何人能够抛出error_base类型,但异常类型error_oh_shuckserror_oh_blast 具有相同的功能,我不想写两次。 pImpl 复杂性对于避免将 std::string 暴露给我的客户端是必要的,并且使用 std::auto_ptr 需要复制构造函数。

公共标头包含异常规范,客户端可使用这些规范来区分我的库引发的不同类型的异常:

// error.h

#include <exception>
#include <memory>

class exception_string;

class error_base : public std::exception {
 public:
  error_base(const char* error_message);
  error_base(const error_base& other);
  virtual ~error_base() = 0; // Not directly usable

  virtual const char* what() const;
 private:
  std::auto_ptr<exception_string> error_message_;
};

template<class error_type>
class error : public error_base {
 public:
   error(const char* error_message) : error_base(error_message) {}
   error(const error& other) : error_base(other) {}
   ~error() {}
};

// Neither should these classes be usable
class error_oh_shucks { virtual ~error_oh_shucks() = 0; }
class error_oh_blast { virtual ~error_oh_blast() = 0; }

这是共享实现:

// error.cpp

#include "error.h"
#include "exception_string.h"

error_base::error_base(const char* error_message)
  : error_message_(new exception_string(error_message)) {}

error_base::error_base(const error_base& other)
  : error_message_(new exception_string(other.error_message_->get())) {}

error_base::~error_base() {}

const char* error_base::what() const {
  return error_message_->get();
}

保持私有的Exception_string类从我的公共接口中隐藏了std::string:

// exception_string.h

#include <string>

class exception_string {
 public:
  exception_string(const char* message) : message_(message) {}

  const char* get() const { return message_.c_str(); }
 private:
  std::string message_;
};

然后我的代码会抛出一个错误:

#include "error.h"

throw error<error_oh_shucks>("That didn't work");

The use of a template for error is a little gratuitous. 它节省了一些代码,但代价是要求客户端捕获错误,如下所示:

// client.cpp

#include <error.h>

try {
} catch (const error<error_oh_shucks>&) {
} catch (const error<error_oh_blast>&) {
}

You asked for an example, and I believe the following provides a reason for a pure virtual destructor. I look forward to replies as to whether this is a good reason...

I do not want anyone to be able to throw the error_base type, but the exception types error_oh_shucks and error_oh_blast have identical functionality and I don't want to write it twice. The pImpl complexity is necessary to avoid exposing std::string to my clients, and the use of std::auto_ptr necessitates the copy constructor.

The public header contains the exception specifications that will be available to the client to distinguish different types of exception being thrown by my library:

// error.h

#include <exception>
#include <memory>

class exception_string;

class error_base : public std::exception {
 public:
  error_base(const char* error_message);
  error_base(const error_base& other);
  virtual ~error_base() = 0; // Not directly usable

  virtual const char* what() const;
 private:
  std::auto_ptr<exception_string> error_message_;
};

template<class error_type>
class error : public error_base {
 public:
   error(const char* error_message) : error_base(error_message) {}
   error(const error& other) : error_base(other) {}
   ~error() {}
};

// Neither should these classes be usable
class error_oh_shucks { virtual ~error_oh_shucks() = 0; }
class error_oh_blast { virtual ~error_oh_blast() = 0; }

And here is the shared implementation:

// error.cpp

#include "error.h"
#include "exception_string.h"

error_base::error_base(const char* error_message)
  : error_message_(new exception_string(error_message)) {}

error_base::error_base(const error_base& other)
  : error_message_(new exception_string(other.error_message_->get())) {}

error_base::~error_base() {}

const char* error_base::what() const {
  return error_message_->get();
}

The exception_string class, kept private, hides std::string from my public interface:

// exception_string.h

#include <string>

class exception_string {
 public:
  exception_string(const char* message) : message_(message) {}

  const char* get() const { return message_.c_str(); }
 private:
  std::string message_;
};

My code then throws an error as:

#include "error.h"

throw error<error_oh_shucks>("That didn't work");

The use of a template for error is a little gratuitous. It saves a bit of code at the expense of requiring clients to catch errors as:

// client.cpp

#include <error.h>

try {
} catch (const error<error_oh_shucks>&) {
} catch (const error<error_oh_blast>&) {
}
望喜 2024-08-05 16:57:51

也许还有另一个纯虚拟析构函数的真实用例,我实际上在其他答案中看不到:)

首先,我完全同意标记的答案:这是因为禁止纯虚拟析构函数需要语言规范中的额外规则。 但这仍然不是 Mark 所要求的用例:)

首先想象一下:

class Printable {
  virtual void print() const = 0;
  // virtual destructor should be here, but not to confuse with another problem
};

以及类似的东西:

class Printer {
  void queDocument(unique_ptr<Printable> doc);
  void printAll();
};

简单地 - 我们有接口 Printable 和一些包含此接口的任何内容的“容器”。 我认为这里很清楚为什么 print() 方法是纯虚拟的。 它可以有一些主体,但如果没有默认实现,纯虚拟是理想的“实现”(“必须由后代类提供”)。

现在想象一下完全相同的情况,只是它不是用于打印而是用于销毁:

class Destroyable {
  virtual ~Destroyable() = 0;
};

而且还可能有一个类似的容器:

class PostponedDestructor {
  // Queues an object to be destroyed later.
  void queObjectForDestruction(unique_ptr<Destroyable> obj);
  // Destroys all already queued objects.
  void destroyAll();
};

它是我的实际应用程序的简化用例。 这里唯一的区别是使用“特殊”方法(析构函数)而不是“普通”print()。 但它是纯虚拟的原因仍然是一样的——该方法没有默认代码。
有点令人困惑的是,必须有一些有效的析构函数,并且编译器实际上为其生成一个空代码。 但从程序员的角度来看,纯虚拟仍然意味着:“我没有任何默认代码,它必须由派生类提供”。

我认为这里没有什么大的想法,只是更多地解释纯虚拟性的工作原理非常一致——对于析构函数也是如此。

Maybe there is another REAL USE-CASE of pure virtual destructor which I actually can't see in other answers :)

At first, I completely agree with marked answer: It is because forbidding pure virtual destructor would need an extra rule in language specification. But it's still not the use case that Mark is calling for :)

First imagine this:

class Printable {
  virtual void print() const = 0;
  // virtual destructor should be here, but not to confuse with another problem
};

and something like:

class Printer {
  void queDocument(unique_ptr<Printable> doc);
  void printAll();
};

Simply - we have interface Printable and some "container" holding anything with this interface. I think here it is quite clear why print() method is pure virtual. It could have some body but in case there is no default implementation, pure virtual is an ideal "implementation" (="must be provided by a descendant class").

And now imagine exactly the same except it is not for printing but for destruction:

class Destroyable {
  virtual ~Destroyable() = 0;
};

And also there could be a similar container:

class PostponedDestructor {
  // Queues an object to be destroyed later.
  void queObjectForDestruction(unique_ptr<Destroyable> obj);
  // Destroys all already queued objects.
  void destroyAll();
};

It's simplified use-case from my real application. The only difference here is that "special" method (destructor) was used instead of "normal" print(). But the reason why it is pure virtual is still the same - there is no default code for the method.
A bit confusing could be the fact that there MUST be some destructor effectively and compiler actually generates an empty code for it. But from the perspective of a programmer pure virtuality still means: "I don't have any default code, it must be provided by derived classes."

I think it's no any big idea here, just more explanation that pure virtuality works really uniformly - also for destructors.

巡山小妖精 2024-08-05 16:57:51

这是一个十年前的话题:)
阅读“Effective C++”一书中第 7 条的最后 5 段了解详细信息,从“偶尔为类提供纯虚拟析构函数会很方便......”开始

This is a decade old topic :)
Read last 5 paragraphs of Item #7 on "Effective C++" book for details, starts from "Occasionally it can be convenient to give a class a pure virtual destructor...."

挽容 2024-08-05 16:57:51

我们需要将析构函数设为虚拟,因为如果我们不将析构函数设为虚拟,那么编译器只会析构基类的内容,所有派生类将保持不变,因为编译器不会调用任何其他类的析构函数。除基类外的类。

we need to make destructor virtual bacause of the fact that , if we dont make the destructor virtual then compiler will only destruct the contents of base class , n all the derived classes will remain un changed , bacuse compiler will not call the destructor of any other class except the base class.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文