简体   繁体   English

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

[英]Why do we need a pure virtual destructor in C++?

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.在其中一篇 C++ 文章中,作者提到当我们想要使类抽象时,我们使用纯虚析构函数。

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?如果是……那为什么?

  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).它只会使基类抽象(感谢@kappa的评论)。

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).注意:析构函数是唯一的方法,即使它纯虚拟的,必须有一个实现才能实例化派生类(是的,纯虚拟函数可以有实现)。

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

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.因此,许多样式指南建议始终使用纯虚拟析构函数来指示一个类是抽象的——如果没有其他原因,它提供了一个一致的地方,阅读代码的人可以查看该类是否是抽象的。

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)需要虚拟析构函数行为(您打算携带指向 ABC 的指针而不是指向派生类型的指针,并通过它们删除)
  • 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:对于我们假设的 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".你保证它不能被实例化(甚至在类本身内部,这就是私有构造函数可能不够的原因),你获得析构函数所需的虚拟行为,并且你不必找到并标记另一个不不需要将虚拟分派作为“虚拟”。

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.例如,假设您的代码中有两个类 myClassA 和 myClassB,并且 myClassB 继承自 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.由于 Scott Meyers 在他的书“更有效的 C++”,第 33 条“使非叶类抽象”中提到的原因,实际上创建一个抽象类 myAbstractClass 是更好的做法,myClassA 和 myClassB 从中继承。 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).在(创建类 myAbstractClass 的)抽象过程中,可能没有 myClassA 或 myClassB 的方法适合成为纯虚方法(这是 myAbstractClass 成为抽象的先决条件)。 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.我有两个类,Numerics/PhysicsParams,它们共享共同的属性。 I therefore let them inherit from the abstract class IParams.因此,我让它们从抽象类 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.例如,setParameter 方法对于每个子类都必须具有相同的主体。 The only choice that I have had was to make IParams' destructor pure virtual.我唯一的选择是使 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; 
};

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 .当您希望没有人能够直接创建 Base 类的对象时,请使用纯虚拟析构函数virtual ~Base() = 0 Usually at-least one pure virtual function is required, let's take virtual ~Base() = 0 , as this function.通常至少需要一个纯虚函数,让我们把virtual ~Base() = 0当作这个函数。

  2. When you do not need above thing, only you need the safe destruction of Derived class object当你不需要上面的东西时,你只需要派生类对象的安全销毁

    Base* pBase = new Derived(); Base* pBase = new Derived(); delete pBase;删除 pBase; pure virtual destructor is not required, only virtual destructor will do the job.不需要纯虚拟析构函数,只有虚拟析构函数才能完成这项工作。

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

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.面向对象设计的基本关系有两种:IS-A和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. IS-A 表示特定对象标识为属于类层次结构中位于它之上的类。 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. Has-a 表示对象是复合类的一部分,并且存在所有权关系。 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.这意味着在 C++ 中,它是一个成员对象,因此拥有类的责任在于在销毁它之前处理它或移交所有权。

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.这两个概念在单继承语言中比在像 c++ 这样的多继承模型中更容易实现,但规则本质上是相同的。 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.当类标识不明确时就会变得复杂,例如将 Banana 类指针传递给采用 Fruit 类指针的函数。

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 关键字是一个编译器指令,用于在类标识不明确时以特定顺序绑定函数。 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. Fruit 类可以有一个虚函数 color() 默认返回“NONE”。 The Banana class color() function returns "YELLOW" or "BROWN". Banana 类 color() 函数返回“YELLOW”或“BROWN”。

But if the function taking a Fruit pointer calls color() on the Banana class sent to it -- which color() function gets invoked?但是,如果采用 Fruit 指针的函数在发送给它的 Banana 类上调用 color() —— 哪个 color() 函数被调用? The function would normally call Fruit::color() for a Fruit object.该函数通常会为 Fruit 对象调用 Fruit::color()。

That would 99% of the time not be what was intended.这在 99% 的情况下都不是预期的。 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.但是如果 Fruit::color() 被声明为虚拟的,那么 Banana:color() 将会为该对象调用,因为正确的 color() 函数将在调用时绑定到 Fruit 指针。 The runtime will check what object the pointer points to because it was marked virtual in the Fruit class definition.运行时将检查指针指向的对象,因为它在 Fruit 类定义中被标记为虚拟。

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.在这种情况下,如果 Fruit 指针只知道它是一个指向 Fruit 的指针,它就会调用 Fruit::color()。

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.在这种情况下,即使指针指向 Fruit 函数也被调用,Banana::color() 也会被调用,因为它是唯一的 color() 实现。

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.约定是将其分配为 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.所以在这种情况下你被禁止创建 Fruit 的实例,但允许创建 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.对指向 Banana 实例的 Fruit 指针的 delete 调用将首先调用 Banana::~Banana(),然后始终调用 Fuit::~Fruit()。 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.如果您编写 C++ 以便只传递精确的类指针,而没有泛型指针或歧义指针,那么实际上并不需要虚函数。 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.但是,如果您需要类型的运行时灵活性(如 Apple Banana Orange ==> Fruit 中),则函数会变得更简单、更通用,并且冗余代码更少。 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.您不再需要为每种类型的水果编写一个函数,而且您知道每种水果都会以自己正确的函数响应 color()。

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.有很多很好的例子可以看,看够了,实际运行它们并弄乱它们,你会得到它。

这是一个十年前的话题:) 阅读“Effective C++”一书中第 7 条的最后 5 段,从“有时给一个类一个纯虚拟析构函数很方便......”开始。

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.我不希望任何人能够抛出error_base类型,但异常类型error_oh_shuckserror_oh_blast具有相同的功能,我不想写两次。 The pImpl complexity is necessary to avoid exposing std::string to my clients, and the use of std::auto_ptr necessitates the copy constructor. pImpl 复杂性对于避免将std::string暴露给我的客户是必要的,并且std::auto_ptr的使用需要复制构造函数。

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 类从我的公共接口隐藏 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_;
};

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. error模板的使用有点无缘无故。 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>&) {
}

Maybe there is another REAL USE-CASE of pure virtual destructor which I actually can't see in other answers :)也许有,我居然无法在其他的答案看纯虚析构函数的另一个REAL用例:)

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 :)但这仍然不是 Mark 要求的用例:)

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.很简单——我们有接口Printable和一些“容器”,里面装着这个接口的任何东西。 I think here it is quite clear why print() method is pure virtual.我认为这里很清楚为什么print()方法是纯虚拟的。 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() .这里唯一的区别是使用了“特殊”方法(析构函数)而不是“普通” 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.我认为这里没有什么大的想法,只是更多地解释了纯虚拟性的工作方式非常一致——对于析构函数也是如此。

1) When you want to require the derived classes to do clean-up. 1) 当您想要求派生类进行清理时。 This is rare.这是罕见的。

2) No, but you want it to be virtual, though. 2) 不,但是您希望它是虚拟的。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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