C++的35個技巧閱讀筆記(三)


系列文章:
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成份""

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章