我对大多数OOP理论都有很好的理解,但最让我困惑的是虚拟析构函数。

我以为析构函数总是被调用,不管是什么,也不管是链中的每个对象。

你打算什么时候让它们虚拟化?为什么?


当前回答

我建议这样做:如果类或结构不是最终的,那么应该为其定义虚拟析构函数。

我知道这看起来像是一种过度警惕的过度杀戮,成为一种经验法则。但是,这是确保从类派生的人在使用基指针删除时不会使用UB的唯一方法。

Scott Meyers在下面引用的有效C++中的建议很好,但不足以确定。

如果一个类有任何虚函数,它应该有一个虚函数析构函数,并且类是否设计为基类设计用于多态性的不应声明虚拟析构函数。

例如,在下面的程序中,基类B没有任何虚拟函数,因此根据Meyer的说法,您不需要编写虚拟析构函数。然而,如果您没有以下UB:

#include <iostream>

struct A
{
    ~A()
    {
        std::cout << "A::~A()" << std::endl;
    }
};

struct B
{
};

struct C : public B
{
    A a;
};

int main(int argc, char *argv[])
{
    B *b = new C;
    delete b; // UB, and won't print "A::~A()"
    return 0;
}

其他回答

通过指向基类的指针调用析构函数

struct Base {
  virtual void f() {}
  virtual ~Base() {}
};

struct Derived : Base {
  void f() override {}
  ~Derived() override {}
};

Base* base = new Derived;
base->f(); // calls Derived::f
base->~Base(); // calls Derived::~Derived

虚拟析构函数调用与任何其他虚拟函数调用都没有区别。

对于base->f(),调用将被分派到Derived::f()中,对于base->~base()也是如此-它的重写函数-将调用Derived::~Derived()。

间接调用析构函数时也会发生同样的情况,例如delete base;。delete语句将调用base->~base(),该函数将被分派到Derived::~Derived()。

具有非虚拟析构函数的抽象类

若您不打算通过指向其基类的指针删除对象,那个么就不需要使用虚拟析构函数。只需保护它,使其不会被意外调用:

// library.hpp

struct Base {
  virtual void f() = 0;

protected:
  ~Base() = default;
};

void CallsF(Base& base);
// CallsF is not going to own "base" (i.e. call "delete &base;").
// It will only call Base::f() so it doesn't need to access Base::~Base.

//-------------------
// application.cpp

struct Derived : Base {
  void f() override { ... }
};

int main() {
  Derived derived;
  CallsF(derived);
  // No need for virtual destructor here as well.
}

什么是虚拟析构函数或如何使用虚拟析构器

类析构函数是一个与~前面的类同名的函数,它将重新分配该类分配的内存。为什么我们需要虚拟析构函数

请参见以下示例中的一些虚拟函数

该示例还说明了如何将字母转换为大写或小写

#include "stdafx.h"
#include<iostream>
using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
  //void convertch(){};
  virtual char* convertChar() = 0;
  ~convertch(){};
};

class MakeLower :public convertch
{
public:
  MakeLower(char *passLetter)
  {
    tolower = true;
    Letter = new char[30];
    strcpy(Letter, passLetter);
  }

  virtual ~MakeLower()
  {
    cout<< "called ~MakeLower()"<<"\n";
    delete[] Letter;
  }

  char* convertChar()
  {
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] + 32;
    return Letter;
  }

private:
  char *Letter;
  bool tolower;
};

class MakeUpper : public convertch
{
public:
  MakeUpper(char *passLetter)
  {
    Letter = new char[30];
    toupper = true;
    strcpy(Letter, passLetter);
  }

  char* convertChar()
  {   
    size_t len = strlen(Letter);
    for(int i= 0;i<len;i++)
      Letter[i] = Letter[i] - 32;
    return Letter;
  }

  virtual ~MakeUpper()
  {
    cout<< "called ~MakeUpper()"<<"\n";
    delete Letter;
  }

private:
  char *Letter;
  bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{
  convertch *makeupper = new MakeUpper("hai"); 
  cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" ";     
  delete makeupper;
  convertch *makelower = new MakeLower("HAI");;
  cout<<"Eneterd : HAI = " <<makelower->convertChar()<<" "; 
  delete makelower;
  return 0;
}

从上面的示例中可以看到,没有调用MakeUpper和MakeLower类的析构函数。

查看下一个带有虚拟析构函数的示例

#include "stdafx.h"
#include<iostream>

using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
//void convertch(){};
virtual char* convertChar() = 0;
virtual ~convertch(){}; // defined the virtual destructor

};
class MakeLower :public convertch
{
public:
MakeLower(char *passLetter)
{
tolower = true;
Letter = new char[30];
strcpy(Letter, passLetter);
}
virtual ~MakeLower()
{
cout<< "called ~MakeLower()"<<"\n";
      delete[] Letter;
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] + 32;

}

return Letter;
}

private:
char *Letter;
bool tolower;

};
class MakeUpper : public convertch
{
public:
MakeUpper(char *passLetter)
{
Letter = new char[30];
toupper = true;
strcpy(Letter, passLetter);
}
char* convertChar()
{

size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] - 32;
}
return Letter;
}
virtual ~MakeUpper()
{
      cout<< "called ~MakeUpper()"<<"\n";
delete Letter;
}
private:
char *Letter;
bool toupper;
};


int _tmain(int argc, _TCHAR* argv[])
{

convertch *makeupper = new MakeUpper("hai");

cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" \n";

delete makeupper;
convertch *makelower = new MakeLower("HAI");;
cout<<"Eneterd : HAI = " <<makelower->convertChar()<<"\n ";


delete makelower;
return 0;
}

虚拟析构函数将显式调用类的最派生的运行时析构函数,以便能够以正确的方式清除对象。

或访问链接

https://web.archive.org/web/20130822173509/http://www.programminggallery.com/article_details.php?article_id=138

我建议这样做:如果类或结构不是最终的,那么应该为其定义虚拟析构函数。

我知道这看起来像是一种过度警惕的过度杀戮,成为一种经验法则。但是,这是确保从类派生的人在使用基指针删除时不会使用UB的唯一方法。

Scott Meyers在下面引用的有效C++中的建议很好,但不足以确定。

如果一个类有任何虚函数,它应该有一个虚函数析构函数,并且类是否设计为基类设计用于多态性的不应声明虚拟析构函数。

例如,在下面的程序中,基类B没有任何虚拟函数,因此根据Meyer的说法,您不需要编写虚拟析构函数。然而,如果您没有以下UB:

#include <iostream>

struct A
{
    ~A()
    {
        std::cout << "A::~A()" << std::endl;
    }
};

struct B
{
};

struct C : public B
{
    A a;
};

int main(int argc, char *argv[])
{
    B *b = new C;
    delete b; // UB, and won't print "A::~A()"
    return 0;
}

当您希望不同的析构函数在通过基类指针删除对象时遵循正确的顺序时,析构函数的虚拟关键字是必需的。例如:

Base *myObj = new Derived();
// Some code which is using myObj object
myObj->fun();
//Now delete the object
delete myObj ; 

如果基类析构函数是虚拟的,那么对象将按顺序被析构函数(首先是派生对象,然后是基)。如果基类析构函数不是虚拟的,那么只有基类对象会被删除(因为指针是基类“base*myObj”)。因此派生对象将存在内存泄漏。

还要注意,在没有虚拟析构函数时删除基类指针将导致未定义的行为。我最近学到的东西:

C++中重写删除应该如何操作?

我已经使用C++多年了,但我还是设法自杀了。