Lod*_*dle 1420 c++ polymorphism shared-ptr virtual-destructor
我对大多数OO理论有了深刻的理解,但让我困惑的一件事是虚拟析构函数.
我认为无论什么以及链中的每个对象,析构函数总是会被调用.
你什么时候打算让它们成为虚拟的?为什么?
Luc*_*lle 1508
当您可能通过指向基类的指针删除派生类的实例时,虚拟析构函数很有用:
class Base
{
// some virtual methods
};
class Derived : public Base
{
~Derived()
{
// Do some important cleanup
}
};
Run Code Online (Sandbox Code Playgroud)
在这里,您会注意到我没有声明Base的析构函数virtual
.现在,我们来看看以下代码段:
Base *b = new Derived();
// use b
delete b; // Here's the problem!
Run Code Online (Sandbox Code Playgroud)
由于Base的析构函数不是virtual
并且b
是Base*
指向Derived
对象,因此delete b
具有未定义的行为:
[In
delete b
],如果要删除的对象的静态类型与其动态类型不同,则静态类型应为要删除的对象的动态类型的基类,静态类型应具有虚拟析构函数或者行为未定义.
在大多数实现中,对析构函数的调用将像任何非虚拟代码一样被解析,这意味着将调用基类的析构函数,但不会调用派生类的析构函数,从而导致资源泄漏.
总而言之,virtual
当它们被多态地操纵时,总是使基类的析构函数成为可能.
如果要防止通过基类指针删除实例,可以使基类析构函数受保护且非虚拟; 通过这样做,编译器将不允许您调用delete
基类指针.
您可以在Herb Sutter的这篇文章中了解有关虚拟性和虚拟基类析构函数的更多信息.
Tun*_*her 200
虚拟构造函数是不可能的,但虚拟析构函数是可能的.让我们实验......
#include <iostream>
using namespace std;
class Base
{
public:
Base(){
cout << "Base Constructor Called\n";
}
~Base(){
cout << "Base Destructor called\n";
}
};
class Derived1: public Base
{
public:
Derived1(){
cout << "Derived constructor called\n";
}
~Derived1(){
cout << "Derived destructor called\n";
}
};
int main()
{
Base *b = new Derived1();
delete b;
}
Run Code Online (Sandbox Code Playgroud)
上面的代码输出如下:
Base Constructor Called
Derived constructor called
Base Destructor called
Run Code Online (Sandbox Code Playgroud)
派生对象的构造遵循构造规则,但是当我们删除"b"指针(基指针)时,我们发现只有基本析构函数被调用.但这不能发生.要做适当的事情,我们必须使基础析构函数成为虚拟的.现在让我们看看下面发生了什么:
#include <iostream>
using namespace std;
class Base
{
public:
Base(){
cout << "Base Constructor Called\n";
}
virtual ~Base(){
cout << "Base Destructor called\n";
}
};
class Derived1: public Base
{
public:
Derived1(){
cout << "Derived constructor called\n";
}
~Derived1(){
cout << "Derived destructor called\n";
}
};
int main()
{
Base *b = new Derived1();
delete b;
}
Run Code Online (Sandbox Code Playgroud)
输出更改如下:
Base Constructor Called
Derived Constructor called
Derived destructor called
Base destructor called
Run Code Online (Sandbox Code Playgroud)
因此,基指针的破坏(在派生对象上进行分配!)遵循破坏规则,即首先导出然后是基数.另一方面,对于构造函数,没有像虚构造函数那样的东西.
Bil*_*ard 185
在多态基类中声明析构函数是虚拟的.这是Scott Meyers的Effective C++中的第7项.迈尔斯继续总结,如果一个类有任何虚函数,它应该有一个虚析构函数,而不是类设计为基类或不是设计用于多态应不声明虚析构函数.
Aby*_*byx 12
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
Run Code Online (Sandbox Code Playgroud)
虚拟析构函数调用与任何其他虚函数调用没有什么不同.
因为base->f()
,调用将被调度Derived::f()
,并且它的base->~Base()
覆盖函数是相同的- Derived::~Derived()
将被调用.
在间接调用析构函数时也是如此,例如delete base;
.该delete
声明将调用base->~Base()
将被发送到的声明Derived::~Derived()
.
如果您不打算通过指向其基类的指针删除对象 - 那么就不需要有虚拟析构函数.只要protected
这样就不会被意外调用:
// 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.
}
Run Code Online (Sandbox Code Playgroud)
我喜欢考虑接口的接口和实现.在C++中,speak接口是纯虚拟类.析构函数是界面的一部分,有望实现.因此析构函数应该是纯虚拟的.构造函数怎么样?构造函数实际上不是接口的一部分,因为对象始终是显式实例化的.
小智 7
简单来说,当您删除指向派生类对象的基类指针时,Virtual析构函数将以正确的顺序销毁资源.
#include<iostream>
using namespace std;
class B{
public:
B(){
cout<<"B()\n";
}
virtual ~B(){
cout<<"~B()\n";
}
};
class D: public B{
public:
D(){
cout<<"D()\n";
}
~D(){
cout<<"~D()\n";
}
};
int main(){
B *b = new D();
delete b;
return 0;
}
OUTPUT:
B()
D()
~D()
~B()
==============
If you don't give ~B() as virtual. then output would be
B()
D()
~B()
where destruction of ~D() is not done which leads to leak
Run Code Online (Sandbox Code Playgroud)
我认为这个问题的核心是关于虚拟方法和多态性,而不是具体的析构函数。这是一个更清楚的例子:
class A
{
public:
A() {}
virtual void foo()
{
cout << "This is A." << endl;
}
};
class B : public A
{
public:
B() {}
void foo()
{
cout << "This is B." << endl;
}
};
int main(int argc, char* argv[])
{
A *a = new B();
a->foo();
if(a != NULL)
delete a;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
将打印出:
This is B.
Run Code Online (Sandbox Code Playgroud)
没有virtual
它会打印出:
This is A.
Run Code Online (Sandbox Code Playgroud)
现在您应该了解何时使用虚拟析构函数。
小智 5
当您希望不同的析构函数在通过基类指针删除对象时遵循正确的顺序时,析构函数的虚拟关键字是必需的.例如:
Base *myObj = new Derived();
// Some code which is using myObj object
myObj->fun();
//Now delete the object
delete myObj ;
Run Code Online (Sandbox Code Playgroud)
如果派生类析构函数是虚拟的,那么对象将按顺序被驱逐(首先是派生对象然后是基础).如果派生类析构函数不是虚拟的,那么只会删除基类对象(因为指针是基类"Base*myObj").因此派生对象会有内存泄漏.
虚拟基类析构函数是“最佳实践” - 您应该始终使用它们来避免(难以检测)内存泄漏。使用它们,您可以确保类的继承链中的所有析构函数都被调用(以正确的顺序)。使用虚拟析构函数从基类继承也会使继承类的析构函数自动成为虚拟的(因此您不必在继承类析构函数声明中重新键入“virtual”)。
如果您使用shared_ptr
(仅shared_ptr,而不是unique_ptr),则不必将基类析构函数设为virtual:
#include <iostream>
#include <memory>
using namespace std;
class Base
{
public:
Base(){
cout << "Base Constructor Called\n";
}
~Base(){ // not virtual
cout << "Base Destructor called\n";
}
};
class Derived: public Base
{
public:
Derived(){
cout << "Derived constructor called\n";
}
~Derived(){
cout << "Derived destructor called\n";
}
};
int main()
{
shared_ptr<Base> b(new Derived());
}
Run Code Online (Sandbox Code Playgroud)
输出:
#include <iostream>
#include <memory>
using namespace std;
class Base
{
public:
Base(){
cout << "Base Constructor Called\n";
}
~Base(){ // not virtual
cout << "Base Destructor called\n";
}
};
class Derived: public Base
{
public:
Derived(){
cout << "Derived constructor called\n";
}
~Derived(){
cout << "Derived destructor called\n";
}
};
int main()
{
shared_ptr<Base> b(new Derived());
}
Run Code Online (Sandbox Code Playgroud)
我建议:如果一个类或结构不是final
,你应该为它定义虚拟析构函数。
我知道这看起来像是一种过度警惕的过度杀戮,成为一条经验法则。但是,这是确保从您的类派生的人在使用基指针删除时不会有 UB 的唯一方法。
下面引用的 Scott Meyers 在《Effective 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;
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
657457 次 |
最近记录: |