溫習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. 禁止獨立對象在對象。