温习More Effective C++,对于Item 27:要求或禁止在堆中产生对象,整理思路于此文。
编译期控制
通过禁用定义对象所需的条件,以在编译期阻止对象的定义。
下表列出了不同位置上不同形式的对象定义所需函数的最低访问权限。
class operator new | 构造函数 | ←堆内对象 堆外对象→ | 构造函数 | class operator new | ||
public | public | 独立形式 | public | private | ||
private | public | 直接成员形式 | public | private | ||
public | public | 指针成员形式 | — | — | ||
private | protected | 基类形式 | protected | private |
注意:
- 通过提供伪构造函数可以让指针对象摆脱对class operator new和构造函数的依赖。
- 不存在非堆对象的指针成员形式。
- 基类对象的class operator new不是必需的,因为派生类可以重载该函数。
- 成员对象的class operator new不是必需的,因为整体类不需要调用它。
结论:
- 独立形式: 通过声明class operator new为private可以禁止对象定义在堆上,如果声明构造函数为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. 禁止独立对象在对象。