C++全局对象的析构函数有时候没有被调用?非也!(编译器为VC++6.0)

本文探讨了VC++6.0中全局对象的构造与析构顺序问题,尤其是在使用不同iostream包时的现象差异。通过实验发现,当使用#include<iostream>并声明全局对象时,其析构函数会在main函数结束后的特定时刻调用,但因cout对象已提前析构而不显示输出。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        说明:以下程序都是在VC++6.0中的结果。

 

        在写程序的时候,我发现一个十分古怪的问题,下面会慢慢道来。原来的程序比较复杂,为了简便起见,只抽出最关键的部分来解释。

        我们首先来看程序:

 

#include <iostream.h>

class Base
{
public:
	Base()
	{
		cout << "Base Constructor" << endl;
	}

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

int main()
{
	Base b;

	return 0;
}

     结果为:

 

Base Constructor
Base Destructor

      把main中的局部对方放倒main外面,得到程序:

 

#include <iostream.h>

class Base
{
public:
	Base()
	{
		cout << "Base Constructor" << endl;
	}

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

Base b;
int main()
{
	return 0;
}

      结果为:

 

Base Constructor
Base Destructor

 

       我们知道,在标准C++中,用#include<iostream.h>显得不纯正,下面我们看程序:

 

#include <iostream>
using namespace std;

class Base
{
public:
	Base()
	{
		cout << "Base Constructor" << endl;
	}

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

int main()
{
	Base b;

	return 0;
}

      结果为:

 

Base Constructor
Base Destructor

 

       此时,如果将main中的局部对方放在前面,程序的结果会是怎样的呢?程序如下:

#include <iostream>
using namespace std;

class Base
{
public:
	Base()
	{
		cout << "Base Constructor" << endl;
	}

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

Base b;
int main()
{
	return 0;
}

      结果为:(注意,并没有Base Destructor):

 

Base Constructor

      奇怪了,怎么会没有Base Destructor呢?难道全局对象的析构函数没有被调用到?我们继续写程序来测试,程序如下:

 

#include <iostream>
using namespace std;

class Base
{
public:
	Base()
	{
		cout << "Base Constructor" << endl;
	}

	~Base()
	{
		cout << "Base Destructor" << endl;
		printf("Base Destructor has been called\n");
	}
};

Base b;
int main()
{
	return 0;
}

        程序的结果为:

 

Base Constructor
Base Destructor has been called

        可见,全局对象的析构函数的确被调用了,但是,为什么没有输出Base Destructor呢?可以这样认为:全局对象的析构函数是在main函数结束后被调用,而cout对象在全局对象析构之前已经被析构,所以cout便没有相应的作用了。

 

        当然,这种现象并不是语言层面的特性,而是跟编译器的具体实现有关。大家可以在更高级的VS版本中进行测试,得到的结果与VC++6.0中得到的结果并不一致。

        总之,在VC++6.0中,如果用到了#include<iostream>和using namespace std; 那么,全局对象的比cout对象更先构造,更后析构。

        建议:在VC++6.0中,如果存在全局对象,那么,在类的析构函数中,强烈建议使用printf来代替cout.  当然,你也可以把#include<iostream>和using namespace std; 换成别扭的#include<iostream.h>

 

<think>好的,我现在需要回答用户的问题:“派生类的析构函数调用时,基类的析构函数会被调用吗?” 用户之前询问过纯虚函数在虚函数表中的情况,现在转向了析构函数调用顺序,这可能涉及继承中的对象生命周期管理。 首先,我要确认用户的问题核心。用户想知道当派生类对象被销毁时,基类的析构函数是否会被自动调用。这个问题涉及到C++对象析构的顺序和机制,特别是继承体系下的析构函数调用规则。 接下来,我需要回顾C++的相关知识。在C++中,当派生类对象被销毁时,析构函数调用顺序与构造函数相反。也就是说,派生类的析构函数首先被调用,然后是基类的析构函数。但是,这个前提是基类的析构函数是虚函数吗?或者是否存在其他条件? 如果基类的析构函数不是虚函数,当通过基类指针删除派生类对象时,可能会导致只有基类的析构函数调用,而派生类的析构函数不被调用,从而导致资源泄漏。因此,虚析构函数的作用在这里很重要。但是用户的问题没有提到是否通过指针删除,而是直接问当派生类析构被调用时,基类的是否会被调用。可能用户的问题更基础,即不考虑多态的情况,仅考虑普通对象销毁时的行为。 在这种情况下,无论基类析构函数是否为虚函数,当派生类对象被销毁时,其析构函数执行完毕后,基类的析构函数会自动被调用。这是因为在继承体系中,对象的构造是从基类到派生类,而析构则是相反的顺序,从派生类到基类。每个派生类的析构函数会负责调用其直接基类的析构函数。 不过,需要注意的是,如果用户手动在派生类的析构函数调用了基类的析构函数,这可能会导致双重析构的问题,因为编译器会自动调用基类析构函数。但这种情况属于编程错误,应该避免。 另外,当使用多态时(即基类指针指向派生类对象),如果基类没有析构函数,那么通过基类指针删除对象时,只会调用基类的析构函数,而不会调用派生类的析构函数。这种情况下,用户的问题可能隐含这种情况,所以需要明确说明不同情况下的行为差异。 总结起来,答案应该是:是的,当派生类对象析构函数调用时,基类的析构函数会被自动调用,前提是对象被正确销毁(例如直接栈对象销毁或通过正确使用虚析构函数的指针删除)。如果基类析构函数是虚函数,则可以确保通过基类指针删除时正确调用派生类和基类的析构函数;否则可能导致派生类析构函数不被调用,但基类的析构函数仍然会被调用。 需要分情况讨论: 1.对象(自动存储期):当派生类对象离开作用域时,其析构函数调用,随后自动调用基类析构函数。 2.对象通过正确类型的指针删除:同样,派生类析构后基类析构被调用。 3. 通过基类指针删除,且基类有虚析构函数:正确调用派生类和基类析构函数。 4. 通过基类指针删除,基类无虚析构函数:未定义行为,通常只调用基类析构函数,可能导致资源泄漏。 用户的问题可能更关注第一种情况,即普通情况下的行为,但作为完整回答,需要涵盖多态情况的影响。 因此,回答的结构应该是先明确正常情况下析构函数调用顺序,然后讨论多态情况下虚析构函数的重要性,以及不同情况下的行为差异。同时,给出代码示例说明不同情况,帮助用户理解。</think>在C++中,**当派生类对象析构函数调用时,基类的析构函数一定会被自动调用**。这是C++对象生命周期管理的核心机制,具体实现遵循以下规则: --- ### 核心机制解析 ```cpp class Base { public: ~Base() { std::cout << "Base destroyed\n"; } }; class Derived : public Base { public: ~Derived() { std::cout << "Derived destroyed\n"; } }; // 使用示例 Derived d; // 对象创建时:先构造Base,再构造Derived // 离开作用域时:先析构Derived,再析构Base ``` #### 控制台输出 ``` Derived destroyed Base destroyed ``` --- ### 三种典型场景分析 #### 场景1:栈对象自动销毁 ```cpp { Derived d; // 自动存储期对象 } // 离开作用域时自动调用析构函数 ``` **调用顺序**: 1. `Derived::~Derived()` 2. `Base::~Base()` #### 场景2:堆对象显式删除 ```cpp Derived* pd = new Derived(); delete pd; // 显式调用析构函数 ``` **调用顺序**: 1. `Derived::~Derived()` 2. `Base::~Base()` #### 场景3:多态对象删除(关键区别) ```cpp Base* pb = new Derived(); delete pb; // 行为取决于基类是否声明虚析构函数 ``` --- ### 虚析构函数的核心作用 ```cpp class Base { public: virtual ~Base() { ... } // 虚析构函数 }; class Derived : public Base { public: ~Derived() override { ... } // 自动成为虚函数 }; ``` #### 正确多态删除时的调用顺序 ``` delete pb; // 当Base有虚析构函数时: 1. Derived::~Derived() 2. Base::~Base() ``` #### 未使用虚析构函数的危险情况 ```cpp class Base { public: ~Base() { ... } // 非虚析构函数 }; Base* pb = new Derived(); delete pb; // 未定义行为!只会调用Base::~Base() // Derived的析构函数和成员不会被清理 ``` --- ### 底层实现原理 #### 析构函数调用编译器会自动生成隐藏代码: ```cpp // 编译器生成的Derived析构函数伪代码 void Derived::~Derived() { // 1. 执行用户编写的析构代码 // 2. 调用成员对象析构函数(逆序) // 3. 调用直接基类的析构函数 Base::~Base(); } ``` #### 虚函数表的影响(当有虚析构函数时) ``` Derived对象虚表: +------------------+ | &Derived::~Derived | // 实际是析构函数包装器 | ...其他虚函数... | +------------------+ 实际调用过程: 1. 通过虚表找到Derived的析构函数 2. 执行Derived的析构逻辑 3. 自动调用Base析构函数 ``` --- ### 设计准则与最佳实践 1. **基类必须声明虚析构函数** - 规则:如果一个类要被多态使用(通过基类指针操作派生类对象),其析构函数必须声明为virtual - 反例:STL容器(如std::string)没有析构函数,因此不应被继承 2. **继承体系中的析构函数声明** ```cpp class AbstractBase { public: virtual ~AbstractBase() = default; // 接口类必须虚析构 }; class Concrete : public AbstractBase { public: ~Concrete() override = default; // 显式标注override }; ``` 3. **不要手动调用析构函数** ```cpp Derived d; d.~Derived(); // 错误!会导致重复析构 ``` --- ### 特殊场景验证 #### 构造函数中抛出异常 ```cpp class Base { public: Base() { throw std::runtime_error(""); } virtual ~Base() = default; }; try { Derived d; // Base构造函数抛出异常 } catch(...) { // 已构造的部分会被正确析构: // 仅调用Base::~Base()(因为Derived构造函数未完成) } ``` #### 虚继承中的析构顺序 ```cpp class A {}; class B : virtual public A {}; class C : virtual public A {}; class D : public B, public C {}; // 析构顺序:D -> C -> B -> A // 虚基类最后析构 ``` --- ### 性能影响分析 | 场景 | 无虚析构函数 | 有虚析构函数 | |-----------------------|--------------|--------------| | 对象大小 | 无变化 | 增加vptr大小 | | 析构函数调用开销 | 静态绑定 | 动态绑定 | | 多态安全性 | 危险 | 安全 | | 适合场景 | 非多态基类 | 多态基类 | --- ### 总结要点 1. **必然调用**:派生类析构后,编译器**自动调用所有直接/间接基类的析构函数** 2. **顺序规则**:析构顺序与构造顺序**严格相反**(派生类→基类,成员变量逆序) 3. **多态前提**:只有通过**虚析构函数**才能保证多态删除时完整调用析构链 4. **标准建议**:被继承的基类必须声明虚析构函数(即使函数体为空)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值