约定对象创建限制
如何限制一个类对象只在堆/栈上分配空间?
为什么需要限制一个类对象只在堆/栈上分配空间?
more effective cpp :条款27
为什么要限制对象创建?
可能你是嵌入式设备,堆空间有限,你希望某类对象不会泄漏内存(不允许在堆上创建,就绝不会内存泄漏,😊)。
可能你想要某些对象有自杀”delete this“的能力,如此安排,对象必须创建在堆上(否则将会被析构两次,调用自杀函数一次,编译器调用一次)。
1 | run.out(8482,0x7ff853a44700) malloc: *** error for object 0x7ff7bf21bf98: pointer being freed was not allocated |
在C++中,类的对象建立分为两种,一种是静态建立,如A a;另一种是动态建立,如A* ptr=new A;
静态建立类对象:由编译器为对象在栈空间中分配内存。通过直接移动栈顶指针,在这片内存空间上调用构造函数形成一个栈对象。使用这种方法,将直接调用类的构造函数。
动态建立类对象:使用new运算符将对象建立在堆空间中。这个过程分为两步,第一步执行operator new()函数,在堆空间中搜索合适的内存并进行分配;第二步调用构造函数构造对象,初始化这片内存空间。这种方法,将间接调用类的构造函数。
只在堆上建立
Non-heap object会在定义点自动构造,在其声明周期结束时自动析构。因此,只需要将那些隐式调用构造函数或析构函数动作非法化,就能限制对象在栈上的建立。
直截了当的方式
将构造函数与析构函数声明为private。
原理:在栈上构建对象时,编译器负责对象的生命周期,自动调用对象的析构函数。如果析构函数为私有,编译器就不能调用对象的析构函数,类对象就无法静态建立。
稍好点的方式
将构造函数声明为public,然后将析构函数声明为private,再导入一个伪析构函数,用于释放对象。
为什么不是将构造函数声明为private?
因为一个类有许多构造函数,析构函数只有一个。编译器添加的构造函数总是public的。
1 | class A { |
但这种方式(将析构函数声明为private)妨碍了继承与包含:派生类无法访问基类的析构函数;包含类无法访问被包含类的析构函数。
1 | class B : public A{ |
解决方式:将基类的析构函数声明为protected可解决继承问题;将成员变量使用方式改为指针,可解决包含问题。
1 | class A { |
更严厉的限制
上述方式,并没有限制派生类在栈上创建。如果更严厉点,要求其派生类也必须在堆上建立,该如何解决?
很遗憾,不能。
more effective cpp 介绍了两种方法:
- 重载operator new,设置onHeap标志,构造函数判断onHeap标志。
- 通过对象地址判断是否位于堆内
遗憾的是,前者无法应对数组(内存一次分配,多次构造);后者无法区分堆对象与静态对象。
只在栈上建立
方法:将operator new() 设为私有
Operator new[]可能也需要设置为private。
1 | class A { |
但是这种方法无法面对继承:
1 | class B : A{ |
包含并无大碍:
1 | class C{ |
更严厉的制约同样无法达到。