只能在堆上生成
为什么需要类只在堆上生成
一般来说,栈是存储局部变量和方法调用的一种内存区域,而堆事用于动态分配内存的另一种内存区域。主要原因有几点
- 对象生命周期:在堆上创建对象可以延长其生命周期。堆上的对象不会受限于其所在方法或作用域的接受。相反,他们在方法结束之后可以先继续存在,并且可以在其他部分使用,适合于多个方法或者多个对象之间需要共享数据。
- 动态大小:堆允许动态分配对象的大小,在堆上创建对象的时候,可以根据需要分配内存,适合于处理链表或者树之类的动态数据结构或者变长数据。
- 对象共享:在堆上创建对象可以实现对象共享。多个对象可以引用相同的堆对象,节省内存。适合于需要复制大量数据。
- 对象的生存期不确定:编译的过程无法确定生存期,放在堆上的时候可以确保对象需要的时候仍然可以使用不会被销毁。
- 引用传递,通常将对象作为参数传递给方法或者函数时,使用引用传递。在堆上创建对象可以轻松使用,不会复制整个对象内容。
注意
适合持久性,共享性和动态大小的对象。对于大型数据结构或者需要大量栈空间的情况,可以考虑使用堆上分配内存或其他数据结构来避免栈溢出。
如何实现
class A{
private:
A(){
//私有构造函数,只能在类内调用
}
public:
static A* createInstance(){
return new A();
}
~A(){
//析构函数,用于在堆上释放对象
}
//其他成员函数和数据成员
}
以上设计,确保只能在堆上生成A类的对象,无法在栈上创建,其他代码可以通过调用A:createInstance()来获取类的实例。 当然由于使用手动的内存管理,需要确保在不再需要对象时显式地删除堆上的对象,避免内存泄露。 上述有点问题,此类不能被继承,可以把A()~A() 设置为protected 模式。
动态生成的步骤
动态建立类对象,是使用new运算符将对象建立在堆空间中,分为两个步骤, 第一步执行operator new()函数,在堆空间中找到合适的内存并进行分配,它不承担构造函数的功能, 第二步是调用构造函数构造对象,初始化内存空间,等于间接调用构造函数。
只能在栈上生成
为什么需要类只在栈上生成
- 生命周期受限:由于栈上对象的生命周期是有限度的,当对象所在的作用域结束时,栈上的对象就会自行销毁,避免手动管理的复杂性。
- 高效性能:栈上对象具有更高的执行效率,栈上的内存分配和释放比在堆上更快,因为栈上只涉及到简单的指针移动,比如循环迭代和递归调用。
- 局部性原理:栈上的对象存储在连续的内存区域中,局部性原理认为程序访问某个内存位置时,很可能访问相邻的内存位置,通过栈上生成对象,可以利用局部性原理,从而提高缓存的命中率和整体运行效率。
- 简单性和可预测性:自动分配和自动释放内存,使得代码更加简洁不容易出现内存泄露。
注意点
- 栈的对象有大小限制,因为栈的空间有限
- 由于生命周期受到限制,栈上的对象不能被多个方法或作用域共享。
- 关于栈的大小,一般来说栈在1MB-8MB ,过大的话占用过多的资源,过小的话栈溢出,所以应该避免过多的递归调用或过深的函数调用,减少占空间的使用。
原理
- c++ 是静态绑定的语言,在编译期间,编译器负责分析所有的非虚函数,当对象建立在栈上面的时候,是有编译器分配内存空间的,调用构造函数来构造栈对象。
- 当对象使用完成后,编译器会调用析构函数来释放所占空。可以说是编译器管理了整个生命周期,
- 具体来说,就是通过移动栈顶指针,空出适当的空间,在这片空间中调用构造函数形成一个栈对象
- 所以编译器在为类对象分配栈空间时,会先检查类的析构函数(包括所有非静态函数)的访问性
- 如果类的析构函数是私有的话,编译器不会在栈空间上为类对象分配内存,所以类的私有析构函数可以保证只能用new命令在堆中创建对象。
class A
{
private:
void *operator new(size_t t){}
void operator delete(void *ptr){}
public:
A(){}
~A(){}
};