如果将对象创建为引用,为什么即使基类析构函数不是虚拟的,也会调用派生类析构函数

Why derived class destructor called even though base class destructor is not virtual if object is created as reference

本文关键字:析构函数 虚拟 派生 调用 基类 创建 对象 引用 为什么 如果      更新时间:2023-10-16
#include <iostream>
class Database
{
public:
   Database()
   {
      std::clog << "Database object created " <<std::endl ;
   }
   ~Database()
   {
      std::clog << "Database object destroyed " << std::endl;
   }
   virtual void Open(const std::string & ) = 0 ; 
} ;
class SqlServer : public Database
{
public:
   void Open(const std::string & conn)
   {
       std::clog << "Attempting to open the connection "<< std::endl ;
   }
      
   ~SqlServer()
   {
        std::clog << "SqlServer:Database object destroyed "<< std::endl ;
   }
} ;
int main()
{
   Database &ref = SqlServer();
   ref.Open("uid=user;pwd=default");
   return 0 ;
}

输出

已创建数据库对象

尝试打开连接

SqlServer:数据库对象已销毁//为什么这被称为析构函数在数据库中不是虚拟

数据库对象已销毁

注意:如果我用 pref 替换 ref,那么一切正常,即 sqlserver 析构函数不会被调用。

这是

涉及对临时const引用的特殊情况。临时的析构函数被正确调用,而不是引用的析构函数,因为毕竟临时的生存期只是延长了。

<小时 />

类似于安德烈·亚历山德雷斯库在他的瞄准镜后卫中使用的技巧。不过,他使用了一个const的临时引用。

根据C++标准,使用临时值初始化的引用会使该临时值在引用本身的生存期内有效。

临时变量的寿命与引用一样长,当它被销毁时,将调用正确的析构函数

来自通用:永远改变你编写异常安全代码的方式

<小时 />

在为什么派生类的析构函数是在对基类的常量引用上调用

的?

您正在将临时绑定到引用。通常这是不允许的,但 MSVC 有一个允许它的邪恶扩展。您可以通过声明const Database &ref = SqlServer();并注释掉ref.Open()行在其他编译器中重现这一点,因为临时可能受 const 引用的约束。

因此,对于 MSVC 中的原始代码或其他编译器中的修改代码,您看到的记录的析构函数消息来自临时销毁的消息。引用使临时保持活动状态,当引用超出范围时,临时引用也会超出范围。

Database &ref = SqlServer();

ref绑定到临时引用,您可以使用 VS 扩展绑定到 const 引用,但最好不要使用它,这些扩展是邪恶的。建议使用智能指针。

class Database
{
public:
   Database()
   {
      std::clog << "Database object created " <<std::endl ;
   }
   ~Database()
   {
      std::clog << "Database object destroyed " << std::endl;
   }
   virtual void Open(const std::string & ) = 0 ; 
   virtual ~Database() {}
} ;

std::unique_ptr<Database> conn(new SqlServer());
conn->Open("uid=user;pwd=default");

注意:数据库类用作基类,但尚未定义virtual destructor 。如果通过指向的指针删除派生类型的对象,则会得到未定义的行为基地。