C++析构函数调用异常问题研究

最近又遇到一个奇葩问题,程序在自己的开发机器和某些机器上运行完好,但是在测试人员的几台机器上运行就直接推出了。开始以为是出现了野指针,因为delete野指针时程序会直接退出。代码翻来覆去过来即便确认没有野指针后问题就陷入了死循环。经过多次调试我发现在我的机器上虽然不崩溃,但是delete对象指针的时候不会走对应的析构函数,这问题就奇怪了。后来终于被我找到了原因。原来在头文件中声明成员变量指针时为了尽量少的包含头文件而使用的前向声明,而在实现文件中又没有包含真正声明该类型的头文件。一般情况下这种使用方式编译器会报错,但是当把指针放在容器中时编译器就不能侦测到错误了。说起来问题比较绕下面用代码写了个例子:

father.h

#ifndef FATHER_H_
#define FATHER_H_

struct FatherCalss
{
public:
    virtual double Speak() = 0;
    virtual ~FatherCalss();

};

struct SonCalss:public FatherCalss
{
public:

    virtual ~SonCalss();

};

class GrandsonClass:public SonCalss
{
public:
    virtual double Speak();

    virtual ~GrandsonClass();
};

#endif//FATHER_H_

father.cpp

#include "stdafx.h"
#include "Father.h"
#include <Windows.h>

FatherCalss::~FatherCalss()
{
    MessageBox(NULL, _T("FatherCalss"), _T("~FatherCalss"), MB_OK);
}

SonCalss::~SonCalss()
{
    MessageBox(NULL, _T("SonCalss"), _T("~SonCalss"), MB_OK);
}

GrandsonClass::~GrandsonClass()
{
    MessageBox(NULL, _T("GrandsonClass"), _T("~GrandsonClass"), MB_OK);
}

double GrandsonClass::Speak()
{
    MessageBox(NULL, _T("Speak"), _T("~GrandsonClass"), MB_OK);
    return 0.0;
}

first.h

#ifndef FIRST_H__
#define FIRST_H__
#include <vector>

struct FatherCalss;

class First
{
public:
    void CreateObject();
    ~First();

    std::vector<FatherCalss*>     m_VecpFather;
};

#endif // First_h__

first.cpp

#include "First.h"
#include "Father.h"

void First::CreateObject()
{
    FatherCalss* pFth = new GrandsonClass;
    pFth->Speak();
    m_VecpFather.push_back(new GrandsonClass);
}

First::~First()
{
    for(std::vector<FatherCalss*>::iterator it = m_VecpFather.begin(); it != m_VecpFather.end(); ++it)
    {
        delete *it;
    }
}

调用代码:

First* pFirst = new First;

pFirst->CreateObject();

for(std::vector<FatherCalss*>::iterator it= pFirst->m_VecpFather.begin(); it != pFirst->m_VecpFather.end(); ++it)
{
    delete *it;
}
pFirst->m_VecpFather.clear();

delete pFirst;

如上如果 m_VecpFather 不是容器而是 FatherCalss* 则编译器会报错。

运行程序后会发现对象的析构函数根本就没有被执行,这样的行为存在一定的不确定性,程序如果不报错的换实际上会产生内存泄漏。

时间: 2024-10-07 23:01:41

C++析构函数调用异常问题研究的相关文章

c++中析构函数调用时机的研究

众所周知,c++中的每个类都会有一个析构函数,当这个类的对象被销毁的时候,对象会自动调用析构函数.那么什么情况下对象的析构函数会被自动调用呢?其实这个问题也可以换种方式问,什么情况下对象会被自动销毁. 我们跟据对象的声明方式分两种情况来讲: 1.动态声明的对象 这种声明方式下系统会自动销毁不再使用的对象,对应的对象的析构函数也会被调用.例如classname object:这样声明的对象,当程序运行到了对象作用域之外或者程序退出,对象都会被销毁,当然析构函数也会被调用. 2.静态声明的对象(ne

c++深/浅拷贝 &amp;&amp; 构造函数析构函数调用顺序练习题

1.深/浅拷贝 编译器为我们提供的合成拷贝构造函数以及合成的拷贝赋值运算符都是浅拷贝.浅拷贝只是做简单的复制,如果在类的构造函数中new出了内存,浅拷贝只会简单的复制一份指向该内存的指针,而不会再开辟内存,这就会使得程序运行出现内存错误,如此,当对象析构的时候,会delete多次同一块内存区域,发生错误.这也就是为什么,必要的时候需要我们自己编写拷贝构造函数和重载赋值运算符,让它变成深拷贝,深拷贝即在copy指针的时候不是简单做值copy,而是还要开辟内存. 2.构造函数析构函数调用顺序练习题

C++语言笔记系列之十五——派生类、基类、子对象的构造和析构函数调用关系

例子 example 1 注:若一个基类同时派生出两个派生类,即两个派生类从同一个基类继承,那么系统将为每一个简历副本,每个派生类独立地使用自己的基类副本(比如基类中有属于自己类的静态变量等). #include <iostream.h> class Person { public: person() {cout<<"Construction of person."<<endl;} ~person() {cout<<"Destr

继承中的构造析构函数调用顺序

子类构造函数必须堆继承的成员进行初始化: 1. 通过初始化列表或则赋值的方式进行初始化(子类无法访问父类私有成员) 2. 调用父类构造函数进行初始化 2.1  隐式调用:子类在被创建时自动调用父类构造函数(只能调用父类的无参构造函数和使用默认参数的构造函数) 2.2  显示调用:在初始化列表调用父类构造函数(适用所有的父类构造函数) #include <iostream> #include <string> using namespace std; class PParent //

避免构造/析构函数调用虚函数(转)

不要在类的构造或者析构函数中调用虚函数,因为这种调用不会如你所愿,即使成功一点,最后还会使你沮丧不已.如果你以前是一个Java或者C#程序员,请密切注意本节的内容-这正是C++与其它语言的大区别之一. 假设你有一个为股票交易建模的类层次结构,例如买单,卖单,等等.为该类交易建立审计系统是非常重要的,这样的话,每当创建一个交易对象,在审计登录项上就生成一个适当的入口项.这看上去不失为一种解决该问题的合理方法: class Transaction {// 所有交易的基类 public: Transa

C++多重继承中构造函数和析构函数调用顺序举例

//多重继承 #include <iostream> using namespace std; class A { public:     A()     {         cout<<"A基类构造A::A()"<<endl;     }     ~A()     {         cout<<"A基类析构A::~A()"<<endl;     } }; class B:public A { publi

c++构造函数析构函数调用顺序

1 #include <iostream> 2 using namespace std; 3 class A 4 { 5 public: 6 A () 7 { 8 cout<<"A 构造 "<<endl; 9 } 10 virtual ~A() // 定义虚析构函数 导致原来析构顺序发生变化 11 { 12 cout<<"A 析构 "<<endl; 13 } 14 15 }; 16 class B : pu

虚函数析构函数调用顺序

析构函数参考:http://blog.csdn.net/zengchen__acmer/article/details/22592195?utm_source=tuicool构造函数参考:http://www.cnblogs.com/nliao/archive/2012/02/05/2339175.html C++构造函数调用顺序: 1.创建派生类的对象,基类的构造函数函数优先被调用(也优先于派生类里的成员类,多继承时,按照派生表的顺序依次调用): 2.如果类里面有成员类,成员类的构造函数优先被

构造函数、析构函数调用虚函数

昨天笔试的时候碰到一个很有意思的题目,大体如下: class Parent { public:     Parent()     {         doit();     }     ~Parent()     {         doit();     }     virtual void doit()     {         cout << "I'm Parent!" << endl;     } }; class Child: public Pare