C++中各种对象的生命周期

作用域由{}定义,可以用构造函数和析构函数来追踪对象的生命周期,比较简单,不述。

全局对象在main开始前被创建,main退出后被销毁。

静态对象在第一次进行作用域时被创建,在main退出后被销毁(若程序不进入其作用域,则不会被创建)。

局部对象在进入作用域时被创建,在退出作用域时被销毁。

new创建的对象会一直存在,即使指向该对象的指针已被销毁,容易造成内存泄漏。(书上说对象会一直存在直到程序退出,因此应该推断,程序退出时对象会销毁,但如书中所说,不是“优雅”地销毁的。)

重点说一下通过实现创建的对象,一般指隐藏的中间临时变量,还有重载+++等操作符也会存在大量隐形临时对象。中间临时变量一般是通过拷贝构造函数创建,不易被察觉,但是往往是造成程序性能下降的瓶颈,尤其是对于占用内存较多,创建速度较慢的对象。我记得以前听fm老师上C++课讲运算符重载,我看到课件里有一条注释说此处用到了拷贝构造,但是怎么看都没看出哪里用到构造函数,于是就去问老师。他说我提醒了他,后来上课专门仔细地讲了一下这个问题,就是关于这种隐形的拷贝构造。听完恍然大悟,这种情况实在太隐蔽了,非常不易察觉。拿书上的例子来说:

class A

{

public:

       A(){printf("A created.\n"); };

       A( A& a ){ printf( “A created with copy.\n”; };

       ~A(){printf( “A destroyed.\n”; };

};

A foo( A a )

{

       A b;

       return b;

}

int main( void )

{

       A a;

       a = foo( a );

       return 0;

}

通过输出可以看出有四个A的对象被创建,其中两个是拷贝构造的。原因是foo()函数的参数和返回值是通过值传递的,因此都要复制一份放入栈中,也就多了两个临时对象,分别是ab的副本,在foo()调用返回后被自动销毁。

如果foo()函数被循环调用,会有大量的对象被创建和销毁。如果对象构造时需要分配很多资源,而资源在析构时又没有被释放,则会造成大量的内存泄漏。即使资源被正确释放,短时间内的资源频繁分配和释放,也会降低性能。

就上面的例子,可以通过传递引用的方式,也就是A foo A& a)来解决参数的临时对象问题,而对于返回时的临时对象,可根据实际,返回指针或引用,或者直接把返回用参数中的指针来包含。

派生类对象的父类对象也是隐含的对象,需要考虑其开销。

对象的内存布局

一个类可能有静态成员数据、非静态成员数据、静态成员函数和非静态成员函数,其中还可能有虚函数。对于类的对象来说:

静态数据成员由所有对象共享,所以sizeof(对象)得到的字节数中,不包括静态数据成员占用的内存。

非静态数据成员是影响对象占据内存大小的主要因素,随对象数目增加而增加。在用sizeof(对象)时有一点需要注意,即内存的对齐,见稍后的例子。

成员函数(静态/非静态)不影响对象内存大小,也不会随对象数目增加而增加。sizeof(对象)得到的字节数中也不包括成员函数,但是成员函数的实现会占据相应的内存空间。

如果对象中包含虚函数,则会在对象占用内存开始处增加4个字节(无论虚函数有多少个)。这4个字节是指向“虚函数表”的指针,即指向一个函数地址表(这个函数地址表具体存储在哪里呢?),函数地址再指向对应的函数实现。这一设置的目的是支持多态性,因为虚函数使得只有在程序运行时,才能决定调用的是父类或是子类中的实现,因此每一个对象有这样一个虚函数表指针。

综上所述,随着对象数目增加而需要增加内存的包括4字节虚函数表指针和非静态数据成员。但要对一个程序运行状态下内存的动态情况进行分析改进,则应该修改函数部分。

一个例子(注意内存对齐):

class simpleClass

{

public:

       static int nCount;

       int nValue;

       char c;

       simpleClass();

       virtual ~simpleClass();

       int getValue( void );

       virtual void foo( void );

       static void addCount();

}

sizeof得到该类对象大小为12字节,从低到高依次是:4字节虚函数表指针、nValue4字节)和c4字节)。注意,c虽然是1个字节,但在32位机上,为了提高效率会按4字节对齐。

书里还提到,虚函数表中不必完全是指向虚函数实现的指针。当指定编译器打开RTTI开关时,虚函数表中第一个指针指向的是一个typeinfo结构,每个类只产生一个typeinfo结构的实例。程序调用typeid()来获取类信息时,实际就是通过虚函数表中的第一个指针获得了typeinfo

  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值