温习More Effective C++,对于Item 27:要求或禁止在堆中产生对象,整理思路于此文。
编译期控制
通过禁用定义对象所需的条件,即可在编译期阻止对象的定义。
- 定义堆对象所需的条件
class operator new | 构造函数 | 析构函数 | |
独立对象 | 非必须 | 不需要,可定义伪构造函数 | 不需要,可定义伪析构函数 |
成员直接对象 | 不需要,整体类定义即可 | 不需要,可定义伪构造函数 | 不需要,可定义伪析构函数 |
成员指针对象 | 不需要,整体类定义即可 | 不需要,可定义伪构造函数 | 不需要,可定义伪析构函数 |
基类对象 | 不需要,派生类定义即可 | public / protected | public / protected |
- 定义非堆对象所需的条件
|||||
|独立非堆对象|不需要|public|public|
|成员非堆对象|不需要|public|public|
|基类非堆对象|不需要|public|public|
注意其中一点,作为成员堆对象,其可以是直接对象也可以是指针对象。而作为成员非堆对象,则只能是直接对象。
通过上表不难得出以下结论:
要求对象在堆中
禁用public构造或析构函数即可使得独立对象和成员对象无法定义在非堆位置,而相应提供伪构造或析构函数就不会影响其在堆中的定义。
然而对于基类对象却没有什么好的办法,因为无论是否在堆中,其定义条件都是相同的——public或protected的构造和析构函数。若声明构造或析构函数为private,基类对象的定义就会被完全禁止,也就是说该类将无法作为基类。
// 数字类。
class Number
{
public:
// 提供伪构造函数。
static Number* MakeInstance() { return new Number; }
virtual ~Number() {}
protected:
// 将构造函数声明为protected。
Number() {}
};
//==============================================================================
// 独立对象。
//==============================================================================
void DefineIndependentObject()
{
//==========================================================================
// 定义在堆中:正确。
//==========================================================================
Number* heapObject = Number::MakeInstance();
delete heapObject;
//==========================================================================
// 定义在栈中:错误。
//==========================================================================
Number nonheapObject;
}
//==============================================================================
// 成员对象。
//==============================================================================
void DefineMemberObject()
{
//==========================================================================
// 定义在堆中:正确。
//==========================================================================
{
// 财产类包含一个数字类指针。
class Asset
{
public:
Asset() : value( Number::MakeInstance() ) {}
~Asset() { delete value; }
private:
Number* value;
};
Asset* heapObject = new Asset;
delete heapObject;
}
//==========================================================================
// 定义在栈中:错误。
//==========================================================================
{
// 财产类包含一个数字类。
class Asset { private: Number value; };
Asset nonheapObject;
}
}
//==============================================================================
// 子类对象
//==============================================================================
void DefineSubclassObject()
{
// 负数类派生自数字类。
class NegativeNumber : public Number {};
//==========================================================================
// 定义在堆中:正确。
//==========================================================================
NegativeNumber* heapObject = new NegativeNumber;
delete heapObject;
//==========================================================================
// 定义在栈中:正确。
//==========================================================================
NegativeNumber nonheapObject;
}
禁止对象在堆中
只需禁用public class new即可禁止独立对象定义在堆中,并且不会对其在非堆位置中的定义产生影响。
然而对于成员对象和基类对象却又不存在什么好的办法,因为只有定义了public构造和析构函数,它们才能被定义在非堆位置,然而这也会使得它们能被定义在堆中。同样的,若是将构造或析构函数定义为private,那么它们的定义将会被完全禁止。
#include <new>
class Number
{
private:
static void* operator new( std::size_t ) throw() { return nullptr; }
};
//==============================================================================
// 独立对象。
//==============================================================================
void DefineIndependentObject()
{
//==========================================================================
// 定义在堆中:错误。
//==========================================================================
Number* heapObject = new Number;
delete heapObject;
//==========================================================================
// 定义在栈中:正确。
//==========================================================================
Number nonheapObject;
}
运行期控制
通过堆对象和非堆对象的不同创建流程来进行控制。其不同之处只有一点:创建堆对象时class operator new会被调用。然而,首先,这一点只对独立对象管用:基类堆对象和成员堆对象的class operator new不一定被调用。
#include <new>
#include <iostream>
class Number
{
public:
static void* operator new( std::size_t ) throw()
{
std::cout << "class operator new for Number" << std::endl;
}
};
class NegativeNumber : public Number
{
public:
static void* operator new( std::size_t ) throw()
{
std::cout << "class operator new for NegativeNumber" << std::endl;
}
};
class Asset { Number value; };
int main()
{
// 独立堆对象的class operator new被调用。
Number* independentObject = new Number;
delete independentObject;
// 看,基类堆对象的则没有被调用。
NegativeNumber* baseObject = new NegativeNumber;
delete baseObject;
// 看,子类堆对象的也没有被调用。
Asset* memberObject = new Asset;
delete memberObject;
return 0;
}
然后,即使是对于独立对象,它也不怎么好用,具体请参考More Effective C++ Item27。
总结
控制对象的内存位置比较困难,能够完美实现的只有:
1. 要求独立对象和成员对象在堆中。
2. 禁止独立对象在对象。