C++中各种对象的生命周期
2013-12-10 23:23
330 查看
作用域由{}定义,可以用构造函数和析构函数来追踪对象的生命周期,比较简单,不述。
全局对象在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()函数的参数和返回值是通过值传递的,因此都要复制一份放入栈中,也就多了两个临时对象,分别是a和b的副本,在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字节虚函数表指针、nValue(4字节)和c(4字节)。注意,c虽然是1个字节,但在32位机上,为了提高效率会按4字节对齐。
书里还提到,虚函数表中不必完全是指向虚函数实现的指针。当指定编译器打开RTTI开关时,虚函数表中第一个指针指向的是一个typeinfo结构,每个类只产生一个typeinfo结构的实例。程序调用typeid()来获取类信息时,实际就是通过虚函数表中的第一个指针获得了typeinfo。
全局对象在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()函数的参数和返回值是通过值传递的,因此都要复制一份放入栈中,也就多了两个临时对象,分别是a和b的副本,在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字节虚函数表指针、nValue(4字节)和c(4字节)。注意,c虽然是1个字节,但在32位机上,为了提高效率会按4字节对齐。
书里还提到,虚函数表中不必完全是指向虚函数实现的指针。当指定编译器打开RTTI开关时,虚函数表中第一个指针指向的是一个typeinfo结构,每个类只产生一个typeinfo结构的实例。程序调用typeid()来获取类信息时,实际就是通过虚函数表中的第一个指针获得了typeinfo。
相关文章推荐
- [祥:]C++定义对象的时候调用默认的构造函数,各种形式
- C++对象模型——Member的各种调用方式(第四章)
- C++对象生命周期
- C++ - 对象模型之各种对象的内存分配、构造和析构
- C++新特性(类与对象的各种指针和引用)
- 各种对象的生命周期(博客转载)
- [C++应用程序性能优化]对象的生命周期
- c++对象的生命周期
- [C++杂记] C++对象的生命周期
- C++——面向对象(三)——各种对象的生存周期
- C++线程安全的对象生命周期管理
- C++:析构函数、对象生命周期、类型转换构造、拷贝构造、拷贝赋值、深浅拷贝
- 探讨c++中的临时对象生命周期
- C++ 匿名对象的生命周期
- C++制作一个泛型容器(可以盛放各种类型的对象)
- C++ 对象生命周期
- C++中 简单查看临时对象,局部对象的生命周期,及拷贝构造函数(测试代码)
- C++各种相似对象的区别
- C++ new创建对象生命周期
- 深度探索C++对象模型---Function语义学之Member的各种调用方式