文章目錄
系列文章:
C++的35個技巧閱讀筆記(一)
C++的35個技巧閱讀筆記(二)
21.通過重載避免隱式類型轉換
利用重載技術時候需要注意,每一個重載的operator必須帶有一個“用戶定製類型”的參數。不要忘了80-20原則,增加一大堆重載函數不一定是件好事,除非使用重載函數後,程序的整體效率獲得重大的改善。
查看條款19
22.考慮以操作符複合形式(op =)取代其單獨形式(op)
要確保操作符的複合形式(operator+=)和其獨身形式(operator+)之間的自然關係能
夠存在,一個好方法就是以前者(+=)爲基礎實現後者(+)
。
class Rational {
public:
Rational& operator+=(const Rational& rhs);
Rational& operator-=(const Rational& rhs);
};
//利用operator+=實現
const Rational operator+(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs) += rhs;
}
//或者使用模板實現
template<class T>
const T operator+(const T& lhs, const T& rhs);
{
return T(lhs) += rhs;
}
1、一般而言,複合操作符比其對應的獨身版本效率高。因爲獨身版本通常必須返回一個新對象,我們必須負擔臨時對象的構造和析構成本。
2、如果同時提供某個操作符的複合形式和獨身形式,便允許你的客戶在有效率與便利性之間做取捨。
23.考慮改變程序庫
不同的程序庫即使提供相似的機能,也往往表現出不同性能取捨策略,所以一旦你找出程序的瓶頸,你應該考慮是否有可能因爲該用另一個程序庫而移除這些瓶頸。
24.瞭解 virtual functions(虛函數)、multiple inheritance(多繼承)、virtual base classes(虛基類)、runtime type identification(運行時類型識別)的代價
- 1、
vtbl(虛函數表)
通常是一個由“函數指針”架構而成的數組,某些編譯器會以鏈表取代數組,但基本策略相同。儘量避免將虛函數聲明爲inline,方法一是探測式,類的虛函數一般產生於內含其第一個non-inline,non-pure虛函數定義式的目標文件中。 - 2、多繼承時,單個對象有多個
vptrs(虛表指針)
,會增加對象體積大小。 - 3、RTTI能讓我們在運行時找到對象和類的有關信息,所以肯定有某個地方存儲了這些信息讓我們查詢,這些信息被存儲在類型爲type_info的對象裏。通常,RTTI被設計爲在類的vbtl上實現。
性質 對象大小增加 Class數據量增加 Inlining機率降低 虛函數 是 是 是 多重繼承 是 是 否 虛擬基類 常常 有時候 否 運行時類型識別 否 是 否
25.將constructor和non-member functions虛化
所謂virtual constructor是某種函數,視其獲得的輸入,課產生不同類型的對象。virtual constructor在許多情況下有用,其中之一就是從磁盤讀取對象信息。
26.限制某個class所能產生的對象數量
- 1、阻止某個class產生對象最簡單的方法是將其constructor 聲明爲private。方法一:讓打印機成爲一個function static,如下所示:
namespace PrintingStuff{
class Printer
{
public:
submitJob(const PrintJob& job);
void reset();
void performSelfTest();
...
friend Printer& thePrinter();// 友元
private:
Printer();
Printer(const Printer& shs);
...
};
Printer& thePrinter()
{
static Printer p; //形成唯一一個Printer對象,是函數中的static 對象而非class中的static對象。
return p;
}
}
//使用了namespace之後我們可以這樣調用
PrintingStuff::thePrinter().reset();
PrintingStuff::thePrinter().submitJob(buffer);
//使用 using PrintingStuff::thePrinter;
thePrinter().reset();
thePrinter().submitJob(buffer);
注意:“class擁有一個static 對象”的意思是:即使從未被使用到,它也會被構造(及析構)。
“函數擁有一個static對象”的意思是:(函數裏面,局部static變量)此對象在函數第一次被調用時才產生。(然而你必須在函數每次調用時檢查對象是否需要誕生)
- 2、利用
numObjects
來追蹤記錄目前存在多少個Printer對象。這個數值在constructor中累加,並在destructor中遞減。如果外界企圖構造太多Printer對象,我們就拋出一個類型爲TooManyObjects的exception。
//將對象計數和僞構造函數結合
class Printer
{
public:
class TooManyObjects();
//僞構造函數
static Printer * makePrinter();
static Printer * makePrinter(const Printer& rhs);
~Printer();
void reset();
...
private:
static size_t numObjects;
static const size_t maxObjects = 10; //設置允許對象個數
//若不支持使用enum{ maxObjects = 10 };或者像numObjects一樣
Printer();
Printer(const Printer& rhs); //若只允許一個時候,不要定義此函數,因爲我們決不允許複製行爲
};
size_t Printer::numObjects = 0;
const size_t Printer::maxObjects;
Printer::Printer()
{
if(numObject >= maxObjects){ //可以自定義限制對象個數。
throw TooManyObjects();
}
proceed with normal construction here;
++numObjects;
}
Printer::Printer(const Printer& rhs)
{
if(numObject >= maxObjects){ //可以自定義限制對象個數。
throw TooManyObjects();
}
proceed with normal construction here;
++numObjects;
}
Printer * Printer::makePrinter()
{ return new Printer; }
Printer * Printer::makePrinter(const Printer& rhs)
{ return new Printer(rhs); }
Printer::~Printer()
{
perform normal destruction here;
--numObject;
}
Printer p1; //錯誤!default ctor 是private;
Printer *p2 = Printer::makePrinter(); //沒問題,間接調用 default ctor;
Printer p3 = *p2; //錯誤!copy ctor是private
p2->reset();
...
delete p2; //避免資源泄漏,如果p2是個auto_ptr此動作不需要
僅僅使用“對象計數”這種方式帶來的問題是:繼承和組合時候,numObjects可能並不能正確表示。Printer對象可於三種不同狀態下生存:(1)它自己(2)派生物的“base class成分”(3)內嵌(組合)於較大對象之中。
“帶有private constructors 的 class 不得被繼承”這一事實導致“禁止派生”的一般性體制。
一個用來計算對象個數的Base Class
template<class BeingCounted>
class Counted
{
public:
class TooManyObjects {};
static int objectCount() { return numObjects; }
protected: //設計作爲base classes
Counted();
Counted(const Count& rhs);
~Counted() { --numObjects; }
private:
static int numObjects;
static const size_t maxObjects;
void init(); //用來避免ctor碼重複出現
};
template<class BeingCounted>
Counted<BeingCounted>::Counted(){ init(); }
template<class BeingCounted>
Counted<BeingCounted>::Counted(const Counted<BeingCounted>&){ init(); }
template<class BeingCounted>
void Counted<BeingCounted>::init()
{
if(numObjects >= maxObjects) throw TooManyObjects();
++numObjects;
}
template<class BeingCounted>
int Counted<BeingCounted>::numObjects; //定義numObject,並自動初始化爲0;const maxObject,留給用戶自己定義。
//Printer class 運用Counted template:
class Printer : private Counted<Printer> //利用Counted template 追蹤目前有多少對象的實現細節最好保持private。
{
public:
static Printer * makePrinter();
static Printer * makePrinter(const Printer& rhs);
~Printer();
using Counted<Printer>::objectCount; //讓此函數對於Printer的用戶而言成爲public的,用戶就可以知道有多少個對象存在
void submitJob(const PrinterJob& job);
void reset();
...
using Counted<Printer>::objectCount;
using Counted<Printer>::TooManyObject;
private:
Printer();
Printer(const Printer& rhs);
};
27.要求(或禁止)對象產生於heap之中
- 1、要求對象產生於heap之中,比較好的方法是讓destructor成爲private,而constructor仍然是public,利用僞destructor函數來調用真正的destructor。但是妨礙了繼承和內含,但是都可以解決:
class UPNumber
{
public:
UPNumber();
UPNmumber(int initValue);
UPNumber(double initValue);
UPNumber(const UPNumber& rhs);
//僞destructor
void destroy() const { delete this; }
...
private:
~UPNumber(); //注意:dtor位於private 區內
};
UPNumber n; //錯誤!(雖然合法,但當n的dtor稍後被隱式調用,就不合法了)
UPNumber *p = new UPNumber; //良好
...
delete p; //錯誤!企圖調用private destructor
p->destroy(); //良好
要繼承時候,可以將private的destructor聲明瞭protected即可。
要內含的話,可以修改爲“內含一個指針,指向UPNumber”。
- 2.判斷某個對象是否位於Heap內?
static對象(涵蓋聲明爲static的對象、global scope 和namespace scope內的對象)在什麼位置?——視系統而定,在許多系統中,如下所示:
Stack向下成長——高地址
Heap向上成長
Static Objects——底地址
判斷指針是否以oeprator new分配出來?——如下
class HeapTracked
{
public:
class MissingAddress{}; //
virtual ~HeapTracked() = 0; //純虛函數使得HeapTracked成爲抽象類
static void *operator new(size_t size);
static void operator delete(void *ptr);
bool isOnHeap() const;
private:
typedef const void* RawAddress;
static list<RawAddress> addresses;
};
list<RawAddress>HeapTracked::addresses;
HeapTracked::~HeapTracked(){} //HeapTracked爲抽象類,但是destructor仍然必須有定義,所以提供一個空定義
void * HeapTracked::operator new(size_t size)
{
void *memPtr = ::operator new(size);
addresses.push_front(memPtr);
return memPtr;
}
void HeapTracked::operator delete(void *ptr)
{
list<RawAddress>::iterator ot = find(addresses.begin(), addresses.end(), ptr);
if(it !=addresses.end()){ //如果找到符合條件的元素,移除,並釋放內存
addresses.erase(it); //否則表示ptr 不是operator new 所分配,於是拋出一個exception。
::operator delete(ptr);
}else{
throw MissingAddress();
}
}
bool HeapTracked::isOnHeap() const
{ //取得一個指針,指向*this 所佔內存的起始處;
const void *rawAddress = dynamic_cast<const void*>(this);
list<RawAddress>::iterator it = find(address.begin(), addresses.end(), rawAddress);
return it != addresses.end();
}
- 3.禁止對象產生於heap之中
(1)對象被直接實例化於heap之中
(2)對象被實例化爲derived class object內的“base class 成分”
(3)對象被內嵌於其他對象之中
class UPNumber
{
private:
static void *operator new(size_t size);
static void operator delete(void *ptr);
...
};
UPNumber n1; //可以
static UPNumber n2; //也可以
UPNumber *p = new UPNumber; //錯誤,企圖調用private operator new。
注意:1)因爲對象總是調用operator new,而後者我們可以自行聲明。因此可以將operator new聲明爲private應該足夠了。2)將operator new 聲明爲private,往往也會妨礙UPNumber被實例化爲heap-base derived class objects的“base class成份""