Effective C++

[條款08:別讓異常逃離析構(gòu)函數(shù)]

問題:如果一個(gè)類的析構(gòu)函數(shù)必須執(zhí)行一個(gè)動(dòng)作,而該動(dòng)作可能會(huì)在失敗時(shí)拋出異常,該怎么辦?舉個(gè)例子,假設(shè)使用過一個(gè)class負(fù)責(zé)數(shù)據(jù)庫連接:

class DBConnection
{
public:
    static DBConnection create();   //此函數(shù)返回DBConnection對(duì)象
    void close();           //關(guān)閉聯(lián)機(jī);失敗則拋出異常
};

一個(gè)較佳策略是創(chuàng)建一個(gè)用來管理DBConnection資源的DBConn類,DBConn類自己提供一個(gè)close函數(shù),因而賦予客戶一個(gè)機(jī)會(huì)得益處理“因該操作而發(fā)生的異?!?。DBConn也可以追蹤其所管理的DBConnection是否已被關(guān)閉,若沒有被關(guān)閉,則由DBConn的析構(gòu)函數(shù)關(guān)閉它。這可防止遺失數(shù)據(jù)連接。然而如果DBConnection析構(gòu)函數(shù)調(diào)用close失敗,則可使用“強(qiáng)制結(jié)束程序”或“吞下異?!钡姆椒ǎ?/p>

class DBConn
{
public:
    DBConn();
    ~DBConn();
 
    void close();
private:
    DBConnection db;
    bool closed;
};
 
DBConn::DBConn()
{
}
 
DBConn::~DBConn()
{
    if(!closed)
    {
        try
        {
            db.close();          //關(guān)閉連接
        }
        catch(...)                //如果關(guān)閉動(dòng)作失敗
        {
            寫日志,記下對(duì)close的調(diào)用失敗;  //記錄下來并結(jié)束程序
            ...                //或者吞下異常;
        }
    }
}
 
void DBConn::close()        //供客戶使用的新函數(shù)
{
    db.close();
    closed = true;
}

如果某個(gè)操作可能在失敗時(shí)拋出異常,而又存在某種需要必須處理該異常,那么這個(gè)異常必須來自析構(gòu)函數(shù)以外的某個(gè)函數(shù)。因?yàn)槲鰳?gòu)函數(shù)拋出異常及時(shí)危險(xiǎn),總會(huì)帶來“過早結(jié)束程序”或“發(fā)生不明確行為”的風(fēng)險(xiǎn)。
請(qǐng)牢記:
1、析構(gòu)函數(shù)絕對(duì)不要拋出異常。如果一個(gè)被析構(gòu)函數(shù)調(diào)用的函數(shù)可能拋出異常,析構(gòu)函數(shù)應(yīng)該捕獲異常,然后吞下它們或結(jié)束程序。
2、如果客戶需要對(duì)某個(gè)操作函數(shù)進(jìn)行運(yùn)行期間拋出的異常做出反應(yīng),那么class應(yīng)該提供一個(gè)普通函數(shù)(而非在析構(gòu)函數(shù)中)執(zhí)行操作。

[條款09:絕不在構(gòu)造和析構(gòu)過程中調(diào)用virtual函數(shù)]

不該在構(gòu)造函數(shù)和析構(gòu)函數(shù)期間調(diào)用virtual函數(shù),這一點(diǎn)是C++與jave/C#不同的地方之一。

假設(shè)有一個(gè)class繼承體系,用來模擬股市交易如買進(jìn)、賣出的訂單等等。這樣的交易一定要經(jīng)過審計(jì),所以每當(dāng)創(chuàng)建一個(gè)交易對(duì)象,在審計(jì)日志中也需要?jiǎng)?chuàng)建一筆適當(dāng)記錄。

正確的做法是在基類Transaction內(nèi)將logTransaction函數(shù)改為non-virtual,然后要求派生類構(gòu)造函數(shù)傳遞必要信息給基類Transaction的構(gòu)造函數(shù),這樣那個(gè)構(gòu)造函數(shù)便可安全地調(diào)用non-virtual logTransaction。正確用法如下:

//絕不在構(gòu)造和析構(gòu)過程中調(diào)用virtual函數(shù)

//如有以下繼承體系,希望每創(chuàng)建一個(gè)交易對(duì)象,都會(huì)有一筆日志記錄
class Transaction
{
public:
    Transaction() 
    {
        logTransaction();
    };

    virtual void logTransaction() const = 0;
};

//繼承類,需要實(shí)現(xiàn)logTransaction()
class BuyTransaction : public Transaction
{
public:
    virtual void logTransaction() const;
};

//繼承類,需要實(shí)現(xiàn)logTransaction()
class SellTransaction : public Transaction
{
public:
    virtual void logTransaction() const;
};

//解決方案:logTransaction改為非虛,然后繼承類構(gòu)造函數(shù)傳遞必要的信息給基類構(gòu)造函數(shù)
class Transaction
{
public:
    explicit Transaction(const std::string& logInfo)
    {
        logTransaction();
    }

    void logTransaction(const std::string& logInfo) const; //非虛
};

class BuyTransaction : public Transaction
{
public:
    BuyTransaction(parameters) 
        : Transaction(createLogString(parameters))
    {}

private:
    //靜態(tài)的放置“初期未成熟的buytransaction對(duì)象內(nèi)尚未初始化的成員變量”
    static std::string createLogString(parameters); 
};

注意示例BuyTransaction內(nèi)的private static函數(shù) createlogString的運(yùn)用。比起在成員初值列內(nèi)給予基類所需的數(shù)據(jù),利用輔助函數(shù)創(chuàng)建一個(gè)值傳遞給基類的構(gòu)造函數(shù)往往比較方便(也比較可讀)。令此函數(shù)為static,也就不可能意外指向“初期未成熟的BuyTransaction對(duì)象內(nèi)尚未初始化的成員變量”。這很重要,正是因?yàn)椤澳切┏蓡T變量處于未定義狀態(tài)”,所以在基類構(gòu)造和析構(gòu)期間調(diào)用的virtual函數(shù)不可下降至派生類。
請(qǐng)牢記:
 在構(gòu)造和析構(gòu)期間不要調(diào)用virtual函數(shù),因?yàn)檫@類調(diào)用從不下降至派生類(比起當(dāng)前執(zhí)行構(gòu)造函數(shù)和析構(gòu)函數(shù)的那層)。

[條款10:令operator= 返回一個(gè)reference to *this]

關(guān)于賦值,可以寫成連鎖形式:

int x, y, z;
x = y = z = 15;    //賦值連鎖形式

賦值采用右結(jié)合律,故上述賦值被解析為:

x = (y = (z = 15)); 

為了實(shí)現(xiàn)連鎖賦值,賦值操作符必須返回一個(gè)reference引用指向操作符的左側(cè)實(shí)參。
下面示例是為classes實(shí)現(xiàn)賦值操作符時(shí)應(yīng)該遵循的協(xié)議:

class Widget
{
public:
    ...
    Widget& operator=(const Widget& rhs)    //返回類型是個(gè)reference,指向當(dāng)前對(duì)象
    {
        ...
        return *this;   //返回左側(cè)對(duì)象
    }
    ...
};

這個(gè)協(xié)議不僅適用于以上的標(biāo)準(zhǔn)賦值形式,也適用于所有賦值相關(guān)運(yùn)算,例如:

class Widget
{
public:
    ...
    //這個(gè)協(xié)議適用于+=,-=,*=,等等
    Widget& operator+=(const Widget& rhs)   //返回類型是個(gè)reference,指向當(dāng)前對(duì)象
    {
        ...
        return *this;   
    }
    Widget& operator=(int rhs)  //此函數(shù)也適用,即使此操作符的參數(shù)類型不符合協(xié)定
    {
        ...
        return *this;   
    }
    ...
};

這份協(xié)議被所有內(nèi)置類型和標(biāo)準(zhǔn)程序庫提供的類型如string,vector,complex,trl::shared_ptr或即將提供的類型共同遵守。
請(qǐng)牢記:
  令賦值(assignment)操作符返回一個(gè)reference to *this

條款11:在operator= 中處理“自我賦值”

“自我賦值”發(fā)生在對(duì)象被賦值給自己時(shí):

class Widget
{
    ...
};
 
Widget w;
...
w = w;  //賦值給自己

operator=,不僅不具備“自我賦值安全性”,也不具備“異常安全性”。
讓operator= 具備“異常安全性”往往自動(dòng)獲得“自我賦值安全性”的回報(bào)。因此越來越多的人對(duì)“自我賦值”的處理態(tài)度是不去管它,而把焦點(diǎn)放在實(shí)現(xiàn)“異常安全性”上。
確保代碼不但“異常安全”而且“自我賦值安全”的一個(gè)替代方案是,使用所謂的copy and swap技術(shù)。此技術(shù)和“異常安全性”有密切關(guān)系,它是一個(gè)常見而夠好的operator=撰寫辦法,其實(shí)現(xiàn)方式為:

class Widget
{
public:<br>    ...
    void swap(Widget& rhs);     //交換*this和任rhs的數(shù)據(jù)
    ...
};
 
 Widget& Widget::operator=(const Widget& rhs)
 {
     Widget temp(rhs);  //為rhs數(shù)據(jù)制作一份復(fù)件(副本)
     swap(temp);        //將*this數(shù)據(jù)和上述復(fù)件的數(shù)據(jù)交換
     return *this;
 }

另外一種實(shí)現(xiàn)方式為:

Widget& Widget::operator=(Widget rhs)  //rhs是被傳對(duì)象的一份復(fù)件(副本),注意此處是值傳遞 pass by value
{
    swap(rhs);     //將*this數(shù)據(jù)和復(fù)件的數(shù)據(jù)交換
    return *this;
}

上述實(shí)現(xiàn)方式因?yàn)椋?、某類的copy assignment操作符可能被聲明為“以by value方式接受實(shí)參”;2、以by value方式傳遞東西會(huì)造成一份復(fù)件/副本
此方式犧牲了清晰性,然而將拷貝動(dòng)作從函數(shù)本體移至“函數(shù)參數(shù)構(gòu)造階段”卻可令編譯器有時(shí)生產(chǎn)更高效的代碼

//在operator=中處理自我賦值

class Bitmap {};

//保存一個(gè)指針指向一塊動(dòng)態(tài)分配的bitmap
class Widget
{
public:
    //問題的發(fā)生:
    Widget& operator=(const Widget& rhs)
    {
        delete pb;
        //如果rhs與this指向同一塊內(nèi)存則錯(cuò)誤
        pb = new Bitmap(*rhs.pb);
        return *this;
    }

    //解決方法1:證同測試,
    Widget& operator= (const Widget& rhs)
    {
        if (this == &rhs)
            return *this;
        delete pb;
        //這里可能出現(xiàn)異常問題,如果new發(fā)生異常(不論內(nèi)存不足還是copy構(gòu)造函數(shù)異常)
        //widget會(huì)持有一個(gè)指針指向一塊被刪除的bitmap.
        pb = new Bitmap(*rhs.pb);
        return *this;
    }

    //解決方法2:精心安排語句順序來保證“異常安全”,防止解法1的問題
    Widget& operator=(const Widget& rhs)
    {
        //記住原先指針,再構(gòu)造一個(gè)副本,然后再刪除,即刪除在構(gòu)造之后
        Bitmap* porg = pb;
        pb = new Bitmap(*rhs.pb);
        delete porg;
        return *this;
    }

    
    //解法3:2的一個(gè)替代方案,即copy and swap技術(shù)
    void swap(Widget& rhs)
    {
        //交換數(shù)據(jù)
    }

    Widget& operator=(const Widget& rhs)
    {
        Widget temp(rhs);
        swap(temp);   //交換*this與temp的數(shù)據(jù)
        return *this;
    }

    //3的另一個(gè)變型解法,依賴以下事實(shí)(1) 某類的拷貝賦值操作可能被聲明為"by value"方式接受實(shí)參
    //(2)以by value方式傳遞東西會(huì)造成另一份副本
    Widget* operator=(Widget rhs)  //這里利用by value構(gòu)造一個(gè)副本
    {
        swap(rhs);   //這里是將*this與副本數(shù)據(jù)互換,
        return *this;
    }
private:
    Bitmap* pb;  //指向一個(gè)從堆上分配的對(duì)象
};



請(qǐng)牢記:
  1、確保當(dāng)前對(duì)象自我賦值時(shí)operator= 有良好行為。其中技術(shù)包括比較“來源對(duì)象”和“目標(biāo)對(duì)象”的地址、精心周到的語句順心、以及copy-and-swap。
  2、確定任何函數(shù)如果操作一個(gè)以上的對(duì)象,而其中多個(gè)對(duì)象是同一個(gè)對(duì)象時(shí),其行為仍然正確。

[條款12:復(fù)制對(duì)象時(shí)勿忘其每一個(gè)部分]

設(shè)計(jì)良好的面向?qū)ο笙到y(tǒng)會(huì)將對(duì)象的內(nèi)部封裝起來,只留兩個(gè)函數(shù)負(fù)責(zé)對(duì)象拷貝,即copy構(gòu)造函數(shù)與copy assignment操作符。編譯器會(huì)在必要的時(shí)候?yàn)轭悇?chuàng)建coping函數(shù),并說明這些“編譯器生成版”的行為:將被拷貝對(duì)象的所有成員變量都做一份拷貝。

任何時(shí)候,只要自己實(shí)現(xiàn)派生類的copying函數(shù),則必須很小心的復(fù)制其基類成分。這些成分往往是private私有的,故無法直接訪問它們,因此應(yīng)該讓派送類的coping函數(shù)調(diào)用相應(yīng)的基類函數(shù):

void logCall(const string& funcName);<br> class Customer
 {
 public:   
     Customer(const Customer& rhs);
     Customer& operator=(const Customer& rhs);
 private:
     string name;
     Date lastTranscation;
 };
class PriorityCustomer : public Customer
{
public:
    ...
    PriorityCustomer(const PriorityCustomer& rhs);
    PriorityCustomer& operator=(const PriorityCustomer& rhs);
     ...
private:
    int priority;
};
 
PriorityCustomer ::PriorityCustomer (const PriorityCustomer& rhs) 
    : Customer(rhs),   //調(diào)用基類的copy構(gòu)造函數(shù)
    priority(rhs.priority)
{
    logCall("PriorityCustomer copy constructor");
}
 
PriorityCustomer& PriorityCustomer ::operator = (const PriorityCustomer& rhs) 
{
    logCall("PriorityCustomer copy assignment constructor");
    Customer::operator=(rhs);  //對(duì)基類Customer成分進(jìn)行復(fù)制動(dòng)作
    priority = rhs.priority;
    return *this;
}

當(dāng)編寫一個(gè)copying函數(shù),確保1、復(fù)制所有l(wèi)ocal成員變量,2、調(diào)用所有基類的適當(dāng)?shù)腸opying函數(shù)。
注意兩個(gè)錯(cuò)誤用法:
1、令copy assignment操作符調(diào)用copy構(gòu)造函數(shù)是錯(cuò)誤的,因?yàn)樵谶@就像試圖構(gòu)造一個(gè)已存在的對(duì)象。
2、令copy構(gòu)造函數(shù)調(diào)用copy assignment操作符同樣是錯(cuò)誤的。構(gòu)造函數(shù)用來出事后對(duì)象,而assignment操作符只實(shí)行與已初始化的對(duì)象身上。對(duì)一個(gè)尚未構(gòu)造好的對(duì)象賦值,就像在一個(gè)尚未初始化的對(duì)象身上做“z只對(duì)已初始化對(duì)象才有意義”的事意義。
消除copy構(gòu)造函數(shù)與copy assignment操作符重復(fù)代碼的做法是:建立一個(gè)新的成員函數(shù)給兩者調(diào)用。這樣的函數(shù)往往是private而且被命名為init。這個(gè)策略可以安全消除copy構(gòu)造函數(shù)與copy assignment操作符之間的代碼重復(fù)。
請(qǐng)牢記:
  1、copying 函數(shù)應(yīng)該確保復(fù)制“對(duì)象內(nèi)的所有成員變量”及“所有基類成分”。
  2、不要嘗試以某個(gè)copying函數(shù)實(shí)現(xiàn)另一個(gè)copying函數(shù)。應(yīng)該將共同機(jī)能放進(jìn)第三個(gè)函數(shù)中,并由兩個(gè)copying函數(shù)共同調(diào)用。

[條款13:以對(duì)象管理資源]

所謂資源就是,一旦使用了它,將來必須還給系統(tǒng)。C++最常使用的資源就是動(dòng)態(tài)分配內(nèi)存(如果分配了內(nèi)存卻不釋放,會(huì)導(dǎo)致內(nèi)存泄露),但內(nèi)存只是必須要管理的眾多資源之一。其他常見的資源還包括文件描述器(file descriptors)、互斥鎖(mutex locks)、圖形界面中的字型和筆刷、數(shù)據(jù)庫連接、以及網(wǎng)絡(luò)sockets。不論哪一種資源,重要的是,當(dāng)不再使用它時(shí),必須將它還給系統(tǒng)。

假設(shè)我們使用一個(gè)用來模擬投資行為(例如股票、債券等)的程序庫,其中各式各樣的投資類型繼承自一個(gè)root class Investment:

//以對(duì)象管理資源

//基類
class Investment
{...}

//程序庫通過一個(gè)工廠函數(shù)產(chǎn)生某個(gè)特定的inverstment對(duì)象,返回指針
Investment* createInvestment(); 

//這導(dǎo)致調(diào)用者需要對(duì)這個(gè)返回的指針進(jìn)行刪除
void f()
{
    Investment* pInv = createInvestment();
    //這里中間可能過早返回,或拋出異常,則都不會(huì)執(zhí)行delete,也就會(huì)發(fā)生資源泄漏
    ...
    delete pInv;
}

//解決方法1:使用auto_ptr管理對(duì)象資源
void f()
{
    //這里是RAII,獲得資源后立刻放進(jìn)管理對(duì)象
    std::auto_ptr<Investment> pInv(createInvestment);

    //auto_ptr的復(fù)制行為
    std::auto_ptr<Investment> pInv2(pInv);  //pInv2指向?qū)ο?,pInv=null
    pInv = pInv2;       //pInv指向?qū)ο螅琾Inv2=null

}//離開函數(shù)的時(shí)候,調(diào)用auto_ptr的析構(gòu)函數(shù)確保資源被釋放

//解決方法2:使用tr1::shared_ptr管理對(duì)象資源
void f()
{
    //這里是RAII,獲得資源后立刻放進(jìn)管理對(duì)象
    std::tr1::shared_ptr<Investment> pInv(createInvestment);

    //shared_ptr的復(fù)制行為
    std::tr1::shared_ptr<Investment> pInv2(pInv);  //pInv2, pInv指向同一對(duì)象
    pInv = pInv2;       //pInv,pInv2指向同一對(duì)象

}//離開函數(shù)的時(shí)候,調(diào)用shared_ptr的析構(gòu)函數(shù)確保pInv,pInv2被釋放

//以下行為雖然可以通過編譯器,但是會(huì)有資源泄漏,不應(yīng)該使用
std::auto_ptr<std::string> aps(new std::string[10]);
std::tr1::shared_ptr<std::string> api(new int[1024]);

請(qǐng)牢記:
1、為防止內(nèi)存泄露,請(qǐng)使用RAII對(duì)象,它們?cè)跇?gòu)造函數(shù)中獲得資源并在析構(gòu)函數(shù)中釋放資源。
2、兩個(gè)常被使用RAII class分別是trl1::shared_ptr和auto_ptr。trl1::shared_ptr通常是較佳選擇,因?yàn)槠鋍opy行為比較直觀。若選擇auto_ptr,復(fù)制動(dòng)作會(huì)使被復(fù)制物指向null。trl1::shared_ptr在頭文件<memory>中

[條款14:在資源管理類中小心copying行為]

請(qǐng)牢記:

1、復(fù)制RAII對(duì)象必須一并復(fù)制它所管理的資源,所以資源的copying行為決定RAII對(duì)象的copying行為。

2、普遍常見的RAII class copying行為是:抑制copying、施行引用計(jì)數(shù)法。不過其他行為也可能被實(shí)現(xiàn)。

//資源管理類的copying行為

class Lock
{
public:
    explicit Lock(Mutex* pm) : mutexPtr(pm)
    {
        lock(mutexPtr); //構(gòu)造函數(shù)鎖住資源
    }

    ~Lock()
    {
        unlock(mutexPtr);  //析構(gòu)函數(shù)釋放資源
    }
};

//客戶端的用法
Mutex m;
Lock ml(&m);  //鎖定,執(zhí)行關(guān)鍵區(qū)域內(nèi)的操作
...

//如果進(jìn)行拷貝,可以發(fā)生以下情況:
Lock ml1(&m);
Lock ml2(ml1);

//情況1:禁止復(fù)制,參考條款6的做法,
//1聲明為私有,不定義,如果復(fù)制發(fā)生鏈接錯(cuò)誤
//2建一個(gè)不能拷貝的基類,并私有繼承于它,這將錯(cuò)誤移到編譯期
class Lock : private Uncopyable
{
};

//情況2:引用計(jì)數(shù)法,例如shared_ptr的行為,但當(dāng)資源變?yōu)?時(shí),它的默認(rèn)行為是
//刪除所指物,而如果我們只是釋放鎖定,這種情況下可以利用shared_ptr中可以指定“刪除器”
//的一個(gè)函數(shù)或函數(shù)對(duì)象,當(dāng)引用計(jì)數(shù)為0時(shí),調(diào)用此函數(shù),這個(gè)參數(shù)對(duì)它的構(gòu)造函數(shù)是可有可無的第二個(gè)參數(shù)
class Lock
{
public:
    explicit Lock(Mutex* pm) : mutexPtr(pm, unlock) //unlock即為指定的刪除器
    {
        lock(mutexPtr.get());
    }
private:
    std::tr1::shared_ptr<Mutex> mutexPtr; //用shared_ptr管理這個(gè)對(duì)象,并且指定刪除器,自定義行為
};
//而且以上不再需要析夠函數(shù),因?yàn)槲鰳?gòu)函數(shù)會(huì)在引用計(jì)數(shù)為0時(shí)自動(dòng)調(diào)用shared_ptr的刪除器

//情況3:復(fù)制底部資源,進(jìn)行深度拷貝,例如string類,

//情況4:轉(zhuǎn)移底部所有權(quán),如auto_ptr的行為,資源所有權(quán)從被復(fù)制物轉(zhuǎn)移到目標(biāo)物

[條款15:在資源管理類張?zhí)峁?duì)原始資源的訪問]

請(qǐng)牢記:

1、APIs往往要求訪問原始資源,所以每一個(gè)RAII class應(yīng)該提供一個(gè)“取得其所管理之資源”的辦法。

2、對(duì)原始資源的訪問可能經(jīng)由顯式轉(zhuǎn)換或隱式轉(zhuǎn)換。一般而言顯式轉(zhuǎn)換比較安全,但隱式轉(zhuǎn)換對(duì)客戶比較方便。

//資源管理類中提供對(duì)原始資源的訪問

class Font
{
public:
    explicit Font(FontHandle fh) : f(fh)
    {}

    //可以提供兩種訪問原始資源的方式:
    //1 顯示轉(zhuǎn)換函數(shù),優(yōu)點(diǎn)是安全,缺點(diǎn)是每次調(diào)用都需要訪問該函數(shù)
    FontHandle get() const
    {
        return f;
    }

    //2 隱式轉(zhuǎn)換,重載轉(zhuǎn)換操作符,優(yōu)點(diǎn)不需顯示調(diào)用,會(huì)隱式執(zhí)行,缺點(diǎn):容易出錯(cuò)
    operator FontHandle() const
    {
        return f;
    }

    ~Font()
    {
        releaseFont(f);
    }
private:
    FontHandle f;  //管理的原始字體資源
};

Font f1(getFont());
FontHandle f2 = f1;  //本來是想拷貝Font對(duì)象,卻隱式轉(zhuǎn)換f1為FontHandle,然后進(jìn)行了復(fù)制

[條款16:成對(duì)使用new和delete時(shí)要使用相同的形式]

請(qǐng)牢記:

如果在new表達(dá)式中使用[],必須在相應(yīng)的delete表達(dá)式中也使用[]。 new[] 對(duì)應(yīng) delete[]

如歌在new表達(dá)式中不適用[],一定不要在相應(yīng)的delete表達(dá)式中使用[]。 new 對(duì)應(yīng) delete

當(dāng)使用new時(shí)(即通過new動(dòng)態(tài)生成一個(gè)對(duì)象),有兩件事發(fā)生:第一,內(nèi)存被分配出來(通過名為operator new 的函數(shù));第二,針對(duì)此內(nèi)存會(huì)有一個(gè)(或更多)構(gòu)造函數(shù)被調(diào)用。

當(dāng)使用delete時(shí),也有兩件事發(fā)生:針對(duì)此內(nèi)存會(huì)有一個(gè)(或更多)析構(gòu)函數(shù)被調(diào)用,然后內(nèi)存才被釋放(通過名為operator delete 的函數(shù))。delete的最大問題在于:即將被刪除的內(nèi)存之內(nèi)有究竟存有多少對(duì)象?(即將被刪除的那個(gè)指針,所指的是單一對(duì)象或?qū)ο髷?shù)組?)此問題的答案決定了又多少個(gè)析構(gòu)函數(shù)必須被調(diào)用起來。

單一對(duì)象的內(nèi)存布局不同于對(duì)象數(shù)組的內(nèi)存布局:數(shù)組所用的內(nèi)存包括“數(shù)組大小”記錄,以便delete制定需要調(diào)用多少次析構(gòu)函數(shù)。單一對(duì)象的內(nèi)存則沒有這筆記錄。

delete[]認(rèn)定指針指向一個(gè)數(shù)組,多次調(diào)用析構(gòu)函數(shù)。因此切記 new和delete時(shí)要采取相同形式。

typedef std::string AddressLines[4];  //定義了一個(gè)AddressLines類型,執(zhí)行string [4]

std::string* pal = new AddressLines; //這里分配的是數(shù)組,相當(dāng)于new string[4]

delete pal;  //錯(cuò)誤,但是可能會(huì)發(fā)生
delete [] pal; //正確的形式,但與以上new不對(duì)稱

[條款17:以獨(dú)立語句將newed對(duì)象置入智能指針]

請(qǐng)牢記:

以獨(dú)立語句將newed對(duì)象存儲(chǔ)于(置入)智能指針內(nèi)。如果不這樣做,一旦異常被跑出來,有可能導(dǎo)致難以察覺的資源泄露。

請(qǐng)牢記:
以獨(dú)立語句將newed對(duì)象存儲(chǔ)于(置入)智能指針內(nèi)。如果不這樣做,一旦異常被跑出來,有可能導(dǎo)致難以察覺的資源泄露。

假設(shè)有個(gè)函數(shù)用來處理程序的優(yōu)先權(quán),另一個(gè)函數(shù)用來在某動(dòng)態(tài)分配所得的Widget上進(jìn)行某些帶有優(yōu)先權(quán)的處理:

int priority();    //處理程序優(yōu)先權(quán)的函數(shù)<br>
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);//該函數(shù)在動(dòng)態(tài)分配所得的Widget上進(jìn)行某些帶有優(yōu)先權(quán)的處理。
調(diào)用:
processWidget(new Widget, priority());     //編譯不過!該構(gòu)造函數(shù)是explicit 無法隱式轉(zhuǎn)換為shared_ptr

因此可以寫成:

processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());  //可以編譯通過,但是...可能泄露資源。

后果:一旦發(fā)生異常,可能資源泄露
原因:
在調(diào)用processWidget之前,編譯器必須創(chuàng)建代碼,執(zhí)行三步:
(1)調(diào)用prority()
(2)執(zhí)行"new Widget"
(3)調(diào)用 tr1"shared_ptr構(gòu)造函數(shù)
但是c++調(diào)用順序跟java和c#不同,不是以特定順序完成。priority函數(shù)的調(diào)用有可能在第一、第二或者第三執(zhí)行。當(dāng)在第二位執(zhí)行的情況下:
(1)執(zhí)行"new Widget"
(2)調(diào)用prority()
(3)調(diào)用 tr1"shared_ptr構(gòu)造函數(shù)
若調(diào)用prority時(shí)發(fā)生異常,則"new Widget"返回的指針將會(huì)遺失,這樣會(huì)引發(fā)資源泄露。
解決方案:使用分離語句,分別寫出(1)創(chuàng)建Widget,(2)將它置入一個(gè)智能指針內(nèi),然后再把那個(gè)智能指針傳給processWidget:

std::tr1::shared_ptr<Widget> pw(new Widget);    //在單獨(dú)語句內(nèi)以智能指針存儲(chǔ)newed所得對(duì)象<br>
processWidget(pw, priority());    // 這個(gè)動(dòng)作不會(huì)造成泄露
//以獨(dú)立語句將newed對(duì)象置入智能指針
//有以下函數(shù)
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);

//以下調(diào)用形式,不會(huì)通過編譯,因?yàn)閠r1::shared_ptr的構(gòu)造函數(shù)是explicit函數(shù)
//雖然它接受一個(gè)widget的指針,但是不能進(jìn)行隱式轉(zhuǎn)換,
processWidget(new Widget, priority());

//所以如果使用以下強(qiáng)制轉(zhuǎn)化,可以通過,但這可能有隱式的資源泄漏,在new與調(diào)用shared_ptr構(gòu)造函數(shù)之間
//執(zhí)行priority,如果它發(fā)生異常,則資源泄漏
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());

//解決方案:
std::tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());

因?yàn)榫幾g器對(duì)于”跨越語句的各項(xiàng)操作“沒有重新排列的自由(只有在語句內(nèi)編譯器才擁有那個(gè)自由度)。

[33:避免掩蓋繼承而來的名稱]

//避免掩蓋繼承而來的名稱

//如以下類
class Base
{
private:
    int x;
public:
    virtual void mf1()=0;
    virtual void mf1(int);

    virtual void mf2();
    
    void mf3();
    void mf3(double);
};

class Derived : public Base
{
public:
    virtual void mf1();   //這里會(huì)將基類的mf1,mf1(int)都隱藏掉,遮蓋了名稱
    void mf3(); //這里也如mf1一樣,將基類的兩個(gè)mf3遮蓋
    void mf4();
};

Derived d;
int x;

d.mf1();    //Derived::mf1
d.mf1(x);   //錯(cuò)誤,因?yàn)槔^承類掩蓋了基類的mf1(int)
d.mf2();    //Derived::mf2
d.mf3();    //Derived::mf3
d.mf3(x);   //錯(cuò)誤,掩蓋了基類的mf3(int)

//為了避免名稱被掩蓋,1 public繼承下可以使用using聲明式
class Base
{
private:
    int x;
public:
    virtual void mf1()=0;
    virtual void mf1(int);

    virtual void mf2();
    
    void mf3();
    void mf3(double);
};

class Derived : public Base
{
public:
    using Base::mf1;  //讓base類名為mf1和mf3的所有東西(函數(shù)了變量了)
    using Base::mf3;  //在Derived作用域內(nèi)都可見(并且是public)
    virtual void mf1(); 
    void mf3();
    void mf4();
};
d.mf1();    //Derived::mf1
d.mf1(x);   //沒問題,Base::mf1(int)
d.mf2();    //Derived::mf2
d.mf3();    //Derived::mf3
d.mf3(x);   //沒問題,Base::mf3(int)

//為了避免名稱被掩蓋,2 使用轉(zhuǎn)交函數(shù)繼承一個(gè)函數(shù)
class Base
{
private:
    int x;
public:
    virtual void mf1()=0;
    virtual void mf1(int);
    ...
};

class Derived : private Base
{
public:
    virtual void mf1()
    {
        Base::mf1();   //轉(zhuǎn)交函數(shù),暗自成為inline
    }
};
d.mf1();    //Derived::mf1
d.mf1(x);   //錯(cuò)誤,名稱被掩蓋

[34:非純虛函數(shù)的缺省實(shí)現(xiàn)提供]

//非純虛函數(shù)的缺省實(shí)現(xiàn)提供

class Airport {...};
class Airplane
{
public:
    virtual void fly(const Airport& destination);
    ...
};

void Airplane::fly(const Airport& destination)
{
    缺省代碼,將飛機(jī)飛向指定的目的地
}

class ModelA : public Airplane {...};
class ModelB : public Airplane {...};
//以上繼承體系,A,B中fly的相同行為可以寫入基類的fly,如果兩者飛行方式相同,則不需重新定義fly,可以直接繼承基類的實(shí)現(xiàn)。
//如果有區(qū)別可以調(diào)用基類的,不同的行為可以再在這個(gè)函數(shù)中寫出來
//如果新增一個(gè)飛機(jī)C,且飛行方式不一樣,為了防止忘記重新定義fly函數(shù),即
//基類實(shí)現(xiàn)“提供缺省實(shí)現(xiàn)給derived classes”,但除非它們明確提出才能調(diào)用缺省實(shí)現(xiàn),一種方法是切斷“virtual”函數(shù)接口和“實(shí)現(xiàn)接口”之間的鏈接,如下
class Ariplane
{
public:
    //聲明為純虛的保證子類必須被重寫
    virtual void fly(const Airplane& destination=0)=0;
    ...
protected: //定義為protected是因?yàn)橹挥欣^承類需要此函數(shù),外部不應(yīng)調(diào)用,也不關(guān)心
    //將默認(rèn)行為移至另外一個(gè)函數(shù),使得子類必須顯示調(diào)用該函數(shù)才能繼承默認(rèn)行為
    void defaultFly(const Airport& destination);    
};

void Airplane::defaultFly(const Airport& destination)
{
    提供缺省行為
}

class ModelA : public Airplane
{
public:
    virtual void fly(const Airport& destination)
    {
        defaultFly(destination);
    }
};


class ModelB : public Airplane
{
public:
    virtual void fly(const Airport& destination)
    {
        defaultFly(destination);
    }
};

//這樣新增的moduleC不可能意外繼承fly的實(shí)現(xiàn)版本了,必須自己提供,且如果不繼承
//默認(rèn)行為則要自己寫出來
class ModelC : public Airplane
{
public:
    virtual void fly(const Airport& destination)
    {
        //不繼承默認(rèn)行為,自己實(shí)現(xiàn)
    }
};

//但以上方法有人認(rèn)為不應(yīng)該以不同的方法分別提供接口和實(shí)現(xiàn)繼承,則可以利用
//純虛函數(shù)必須在derived classes中重新聲明,但他們也可擁有自己的實(shí)現(xiàn)這個(gè)事實(shí)
//給基類的純虛fly函數(shù)提供一個(gè)缺省實(shí)現(xiàn)
class Ariplane
{
public:
    //聲明為純虛的保證子類必須被重寫
    virtual void fly(const Airplane& destination=0)=0;
    ...
};

void Airplane::fly(const Airport& destination)
{
    提供缺省行為
}

class ModelA : public Airplane
{
public:
    virtual void fly(const Airport& destination)
    {
        Airplane::fly(destination);
    }
};


class ModelB : public Airplane
{
public:
    virtual void fly(const Airport& destination)
    {
        Airplane::fly(destination);
    }
};

//這樣新增的moduleC不可能意外繼承fly的實(shí)現(xiàn)版本了,必須自己提供,且如果不繼承
//默認(rèn)行為則要自己寫出來
class ModelC : public Airplane
{
public:
    virtual void fly(const Airport& destination)
    {
        //不繼承默認(rèn)行為,自己實(shí)現(xiàn)
    }
};

[35:virtual函數(shù)替代方案,例子]

//virtual函數(shù)替代方案,例子

//游戲人物的基類,會(huì)有不同的繼承類
class GameCharacter
{
public:
    //返回人物健康指數(shù),不是純虛,說明是有一個(gè)計(jì)算健康指數(shù)的缺省算法
    virtual int healthValue() const;  
};

//方案1:NVI手法
class GameCharacter
{
public:
    int healthValue() const
    {
        //可以做一些事前工作,如包括鎖定互斥器,制造運(yùn)轉(zhuǎn)日志記錄,驗(yàn)證classu
        //約束條件,驗(yàn)證函數(shù)先決條件等等
        ...
        int retVal = doHealthValue();
        ...
        //調(diào)用結(jié)束后還可以做一些事后工作,包括互斥器接觸鎖定,驗(yàn)證函數(shù)的事后
        //條件,再次驗(yàn)證類約束條件
        return retVal;
    }
private:
    virtual int doHealthValue() const   //繼承類可以重定義它
    {
        ...//缺省算法,計(jì)算健康指數(shù)
    }
};

//2 成員函數(shù)指針實(shí)現(xiàn)stategy模式
class GameCharactor;  //前置聲明
//計(jì)算健康指數(shù)的缺省算法
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter
{
public:
    //定義函數(shù)類型
    typedef int (*HealthCalcFunc)(const GameCharacter&);
    explicit GameCharacter(HealthCalcFunc hcf=defaultHealthCalc)
        : HealthFunc(hcf)
    {}

    int healthValue() const
    {
        return healthFunc(*this);
    }
private:
    HealthCalcFunc heathFunc;
};
//GameCharacter可提供一個(gè)成員函數(shù)setHealthCalculator,
//用來替換當(dāng)前的健康指數(shù)計(jì)算函數(shù),則函數(shù)可以在運(yùn)行時(shí)變更

//同一人物類型的不同實(shí)體可以有不同的健康計(jì)算函數(shù)
class EvilBadGuy : public GameCharacter
{
public:
    explicit EvilBadGuy(HealthCalcFunc hcf=defaultHealthCalc)
        : GameCharacter(hcf)
    {...}
};

//不同的健康指數(shù)計(jì)算函數(shù)
int loseHealthQuickly(const GameCharacter&); 
int loseHealthSlowly(const GameCharacter&);

//相同類型的人物搭配不同的健康計(jì)算方式
EvilBadGuy ebg1(loseHealthQuickly);
EvilBadGuy ebg2(loseHealthSlowly);

//方案3:tr1::function函數(shù)實(shí)現(xiàn)strategy模式
class GameCharactor;  //前置聲明
//計(jì)算健康指數(shù)的缺省算法
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter
{
public:
    //代表函數(shù)是接受一個(gè)reference指向const GameCharacter,并返回int,而且這個(gè)
    //函數(shù)對(duì)象可以持有任何與此前面式兼容的可調(diào)用物,即參數(shù)可被隱式轉(zhuǎn)換為
    //const GameCharacter&,返回值可被隱式轉(zhuǎn)換為int,這個(gè)也是與方案2類的唯一
    //區(qū)別,不是持有一個(gè)指針,而是持有一個(gè)tr1::function對(duì)象,相當(dāng)于指向函
    //數(shù)的泛化指針
    typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;
    explicit GameCharacter(HealthCalcFunc hcf=defaultHealthCalc)
        : HealthFunc(hcf)
    {}

    int healthValue() const
    {
        return healthFunc(*this);
    }
private:
    HealthCalcFunc heathFunc;
};
//健康計(jì)算函數(shù):返回是是non-int,但是可以隱式轉(zhuǎn)換為int,可調(diào)用
short calcHealth(const GameCharacter&);
//函數(shù)對(duì)象
struct HealthCalculator
{
    int operator() (const GameCharacter&) const
    {...}
};

class GameLevel
{
public:
    //成員函數(shù),用于計(jì)算健康值,non-int返回類型
    float health(const GameCharacter&) const;
};

//不同的人物類型,構(gòu)造函數(shù)相同
class EvilBadGuy : public GameCharacter
{};
class EyeCanyCharacter : public GameCharacter
{};

EvilBadGuy ebg1(calHealth);  //人物1,使用函數(shù)計(jì)算
EyeCandyCharacter ecc1(HealthCalculator()); //人物2,使用函數(shù)對(duì)象計(jì)算
//使用類的成員函數(shù)計(jì)算
GameLevel currentLevel;
EvilBadGuy ebg2(str::tr1::bind(&GameLevel::health, currentLevel, _1));

//方案4:古典的Strategy模式
class GameCharacter;
class HealthCalcGunc
{
public:
    ...
    virtual int calc(const GameCharacter& gc) const
    {}
};

class SlowHealthLoser : public HealthCalcFunc
{};
class FastHealthLoser : public HealthCalcFunc
{};

HealthCalcFunc defaultHealthCalc;
class GameCharacter
{
public:
    explicit GameCharacter(HealthCalcFunc* phcf = &defaultHealthCalc)
        : pHealthCalc(phcf)
    {}
    int healthValue() const
    {
        return pHealthCalc->calc(*this);
    }
private:
    HealthCalcFunc* pHealthCalc;
};
//不同的人物類型,構(gòu)造函數(shù)相同
class EvilBadGuy : public GameCharacter
{};
class EyeCanyCharacter : public GameCharacter
{};

[37:不要重寫定義繼承而來的缺省參數(shù)值]

//不要重寫定義繼承而來的缺省參數(shù)值

//以下有缺省參數(shù)值調(diào)用時(shí)可不指定參數(shù)
class Shape
{
public:
    enum ShapeColor {Red, Green, Blue};
    virtual void draw(ShapeColor color = Red) const = 0;
};

class Rectangle : public Shape
{
public:
    //這里賦予了不同的參數(shù)值,很糟糕,雖可以不帶參數(shù)調(diào)用,卻會(huì)造成不同
    virtual void draw(ShapeColor color = Green) const;
};

class Circle : public Shape
{
public:
    //客戶端如果調(diào)用下面這個(gè)函數(shù),有兩種情況:1 以對(duì)象調(diào)用,需指名參數(shù)值,
    //因?yàn)閷?duì)象調(diào)用是靜態(tài)綁定并不會(huì)從基類繼承缺省參數(shù)值 2 以指針或reference
    //調(diào)用,可以不指定參數(shù)值,因?yàn)閯?dòng)態(tài)綁定下這個(gè)函數(shù)會(huì)從其基類繼承缺省參數(shù)值
    virtual void draw(ShapeColor color) const;
};

//以下靜態(tài)類型都是Shape*
Shape* ps;                //動(dòng)態(tài):未確定,未指向任何對(duì)象
Shape* pc = new Circle;   //動(dòng)態(tài)類型是Circle, 
Shape* pr = new Rectangle;//動(dòng)態(tài)類型是Rectangle

//動(dòng)態(tài)類型在程序執(zhí)行過程中可以改變
ps = pc;
ps = pr;

pc->draw(Shape::Red)  //Circle::Draw(Shape::Red)
pr->draw(Shape::Red)  //Rectange::Draw(Shape::Red)

//因?yàn)镽eactangle的draw有缺省參數(shù)值,所以這個(gè)調(diào)用正確,但是因?yàn)閜r的靜態(tài)值是
//Shape*,而缺省參數(shù)是靜態(tài)調(diào)用的,所以這個(gè)的默認(rèn)參數(shù)不是Rectangle中的Green,
//而是Shape中draw的Red,結(jié)果造成這個(gè)函數(shù)基類與繼承類各出一半力,奇怪的行為
pr->draw();

//NVI手法實(shí)現(xiàn)缺省參數(shù)值,防止虛函數(shù)這種表現(xiàn)出異常的行為
class Shape
{
public:
    enum ShapeColor {Red, Green, Blue};
    //非虛調(diào)用一個(gè)虛函數(shù)完成人物,提供缺省參數(shù)
    void draw(ShapeColor color = Red) const = 0
    {
        doDraw(color);
    }
private:
    //繼承類需提供實(shí)現(xiàn)
    virtual void doDraw(ShapeColor color) const = 0;
};

class Rectangle : public Shape
{
public:
    //這里不指定缺省參數(shù)值
    virtual void doDraw(ShapeColor color) const;
};

[39:明智而審慎的使用private繼承]

//明智而審慎的使用private繼承

class Timer
{
public:
    explicit Timer(int tickFrequency);
    //定時(shí)器每滴答一次,函數(shù)就自動(dòng)被調(diào)用
    virtual void onTick() const;       
};

//Widget不是Timer,Timer只是幫助實(shí)現(xiàn)它,所以私有繼承,函數(shù)onTick在Widget變成
//私有的,且把它放在私有域中,如果public則有可能調(diào)用阿
class Widget : private Timer
{
private:
    virtual void onTick() const; //查看Widget的數(shù)據(jù)等等, 
};

//使用復(fù)合實(shí)現(xiàn)以上方法
class Widget
{
private:
    //使用一個(gè)私有的嵌套類,而這個(gè)類共有繼承Timer,并實(shí)現(xiàn)onTick方法
    class WidgetTimer : public Timer
    {
    public:
        virtual void onTick() const;
    };
    WidgetTimer timer;   //在包含一個(gè)嵌套類對(duì)象,則可以調(diào)用了呀
};

//空類例子
class Empty {};   //C++插入一個(gè)char成為一個(gè)字節(jié)的大小

//以下這個(gè)類不再是一個(gè)int大小,還包括empty的1字節(jié),如果還有對(duì)齊,那就會(huì)有
//3個(gè)padding了,多一個(gè)int了,sizeof(HoldAnInt) > sizeof(int)
class HoldsAnInt
{
private:
    int x;
    Empty e;
};

//如果繼承一個(gè)空類,則有空白基類最優(yōu)化的情況發(fā)生EBO
//以下empty不占空間,sizeof(HoldAnInt) == sizeof(int)
class HoldsAnInt : private Empty
{
private:
    int x;
};

[42:嵌套從屬類型]

//typename的雙重意義

//嵌套從屬類型
template <typename C>
print2nd(const C& container)
{
    //以下是一個(gè)嵌套從屬名稱,因?yàn)镃是模板參數(shù),并且const_iterator被嵌套于
    //這個(gè)C中,所以下面需要使用typename,表明它是一個(gè)類型
    typename C::const_iterator* x;
}

//typename只被用來驗(yàn)明嵌套從屬類型名稱,其他名稱不該有它存在
template <typename C>   //允許使用typename與class,此中情況下等同
void f(const C& container,  //正常類型,不允許使用typename
        typename C::iterator iter);  //嵌套類型,需要使用typename

//特殊情況,typename不可以出現(xiàn)在基類列表內(nèi)的嵌套從屬類型名稱之前,也不可在
//成員初始化列表中作為base class修飾符
template <typename T>
class Derived : public Base<T>::Nested //基類列表中不允許
{
public:
    explicit Derived(int x)
        : Base<T>::Nested(x)   //成員初始化列表中不允許
    {
        typename Base<T>::Nested temp;  //嵌套從屬類型加typename
    }
};

//STL中typename
template <typename IterT>
void workWithIterator(IterT iter)
{
    //為嵌套從屬類型使用一個(gè)類型聲明符,value_type就代表這個(gè)類型
    typedef typename std::iterator_traits<IterT>::value_type value_type;
    value_type temp(*iter);
}

[49:了解new-handler的行為]

//了解new-handler的行為

//set_new_handler函數(shù)
namespace std {
    typedef void (*new_handler)();
    new_handler set_new_handler(new_handler p) throw();  //異常聲明,表示該函數(shù)不拋出任何異常
}

//set_new_handler用法
//以下是operator new無法分配內(nèi)存時(shí),該被調(diào)用的函數(shù)
void outOfMem()
{
    std::cerr << "Unable to satisfy request for memory\n";
    std::abort();
}

int main()
{
    std::set_new_handler(outOfMem); //安裝內(nèi)存處理函數(shù)
    int *pBigDataArray = new int [100000000L];  //當(dāng)無法分配時(shí),調(diào)用outOfMem;
    ...
}


//提供類專屬之new-handler,
class Widget
{
public:
    //客戶端需要提供一個(gè)new-handler函數(shù)專門是該類使用的,
    static std::new_handler set_new_handler(std::new_handler p) throw();
    static void* operator new(std::size_t size) throw(std::bad_alloc); //只拋出bad_alloc異常
private:
    static std::new_handler currentHandler;  //指向當(dāng)前類使用的new-handler,保存它是為了恢復(fù)
};

std::new_handler Widget::currentHandler = 0; //初始化0

//客戶端需要提供一個(gè)new-handler函數(shù)專門是該類使用的,該函數(shù)獲得這個(gè)指針并存儲(chǔ)起來,然后返回先前存儲(chǔ)的指針。
//這也正式標(biāo)準(zhǔn)的set_new_handler的行為
static std::new_handler Widget::set_new_handler(std::new_handler p) throw();
{
    std::new_handler oldHandler = currentHandler;
    currentHandler = p;
    return oldHandler;
}

//來管理Widget的new-handler,保證可以正?;謴?fù)原來的handler
class NewHandlerHolder
{
public:
    explicit NewHandlerHolder(std::new_handler nh) //取得目前的new-handler
        : handler(nh)
    {}

    ~NewHandlerHolder()
    {
        std::set_new_handler(handler);  //釋放它,其實(shí)就是還原了handler
    }
private:
    std::new_handler handler;  //記錄下來

    NewHandlerHolder(const NewHandlerHolder&);  //阻止拷貝
    NewHandlerHolder& operator=(const NewHandlerHolder&);
};

//實(shí)現(xiàn)了以上類,則就可以實(shí)現(xiàn)Widget's operator new,實(shí)現(xiàn)很簡單
void *Widget::operator new(std::size_t size) throw(std::bad_alloc)
{
    //安裝Widget的new-handler,并且保存原來的handler,如果第一次調(diào)用,原來的是null
    NewHandlerHolder h(std::set_new_handler(currentHandler));

    return ::operator new(size);  //分配內(nèi)存或拋出異常,回復(fù)global new-handler(調(diào)用NewHandlerHolder的析構(gòu)函數(shù)恢復(fù))       
}


//客戶端可能的調(diào)用:
void outOfMem();  //為類聲明的handler
Widget::set_new_handler(outOfMem); //設(shè)置outOfMem為Widget內(nèi)存分配失敗時(shí)調(diào)用的函數(shù)

Widget *pw1 = new Widget;  //如果內(nèi)存分配失敗,調(diào)用outOfMem

std::string* ps = new std::string;  //如果內(nèi)存分配失敗,調(diào)用global new-handler,如果有的話

Widget::set_new_handler(0);  //設(shè)置Widget專屬的為NULL
Wdiget* pw2 = new Widget;    //如果內(nèi)存分配失敗,立刻拋出異常

//適用于每個(gè)類的方案
template <typename T>
class NewHandlerSupport
{
public:
    static std::new_handler set_new_handler(std::new_handler p) throw();
    static void* operator new(std::size_t size) throw(std::bad_alloc);
private:
    static std::new_handler currentHandler;
};

template <typename T>
std::new_handler
NewHandlerSuppport<T>::set_new_handler(std::new_handler p) throw()
{
    std::new_handler oldHandler = currentHandler;
    currentHandler = p;
    return oldHandler;
}

template <typename T>
void* NewHandlerSupport<T>::operator new(std::size_t size) throw(std::bad_alloc)
{
    NewHandlerHolder h(std::set_new_handler(currentHandler));
    
    return ::operator new(size);
}

//以下將每一個(gè)currentHandler初始化為NULL
template <typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;

//只要Widget繼承自NewHandlerSupprt<Widget>就可以實(shí)現(xiàn)為該類提供一個(gè)專屬的handler的功能了
class Widget : public NewHandlerSupport<Widget>
{
    ...
};

//nothrow new
class Widget {...};
Widget* pw1 = new Widget;  //正常的,分配失敗返回bad_alloc
if (pw1 == 0) ... //測試失敗

Widget* pw2 = new (std::nothrow) Widget; // 如果分配失敗,返回0
if (pw2 == 0) ... //測試可能成功. 


[50:定制型operator new]

//定制型operator new

static const int signature = 0xDEADBEEF;
typedef unsigned char Byte;

void* operator new(std::size_t size) throw(std::bad_alloc)
{
    using namespace std;
    size_t realSize = size + 2 * sizeof(int);  //增加大小,使其可以塞入兩個(gè)signatures

    void* pMem = malloc(realSize);
    if (!pMem)
        throw bad_alloc();

    //將signature寫入內(nèi)存的最前段落與最后段落
    *(static_cast<int*>(pMem)) = signature;
    *(reinterpret_cast<int*>(static_cast<Byte*>(pMem) + realSize - sizeof(int))) = signature;

    //返回指針,指向位于第一個(gè)signature之后的內(nèi)存位置
    return static_cast<Byte*>(pMem) + sizeof(int);
}

[51:編寫new和delete時(shí)需固守常規(guī)]

//編寫new和delete時(shí)需固守常規(guī)

void* operator new(std::size_t size) throw(std::bad_alloc)
{
    using namespace std;
    if (size == 0)  //如果是0 byte, 則當(dāng)成1byte
        size = 1;

    //循環(huán)嘗試分配size bytes;
    while (true)
    {
        嘗試分配size bytes;
        if (分配成功)
            return (一個(gè)指針,指向分配得來的內(nèi)存);

        //分配失敗,找出目前的new-handling函數(shù),因?yàn)闆]有直接獲得該值的函數(shù),
        //所以使現(xiàn)在的handler=null,利用其返回值返回以前的handler;
        new_handler globalHandler = set_new_handler(0); 
        set_new_handler(globalHandler);  //安裝

        //如果非空,則調(diào)用,失敗則拋出異常
        if (globalHandler)
            (*globalHandler)();
        else
            throw std::bad_alloc();
    }
}

class Base
{
public:
    static void* operator new(std::size_t size) throw(std::bad_alloc);
    static void* operator delete(void* rawMemory, std::size_t size) throw();
    ...
};

//假設(shè)類未聲明operator new,則會(huì)繼承基類的operator new
class Derived : public Base
{
    ...
};

Derived* p = new Derived; //調(diào)用基類的operator new,但基類專屬的operator new并非用來對(duì)付上述這種大小的對(duì)象

//解決方案,operator new類版本
void* Base::operator new(std::size_t size) throw(sts::bad_alloc)
{
    if (size != sizeof(Base))   //如果大小錯(cuò)誤,則調(diào)用標(biāo)準(zhǔn)的operator new,這里省略了對(duì)0的判斷,因?yàn)閟izeof(類)不會(huì)返回0
        return ::operator new(size);
    ...
}

//定制operator delete
void operator delete(void* rawMemory) throw()
{
    if (rawMemory == 0) //如果被刪除的是個(gè)null指針,則什么都不做
        return;
    以下歸還rawMemory內(nèi)存
}

//operator delete類版本
void Base::operator delete(void* rawMemory, std::size_t size) throw()
{
    if (rawMemory == 0)  //如果為null,什么都不做
        return;
    if (size != sizeof(Base))  //如果大小錯(cuò)誤,調(diào)用標(biāo)準(zhǔn)版
    {
        ::operator delete(rawMemory);
        return;
    }

    現(xiàn)在,歸還rawMemory所指內(nèi)存
    return;
}

[52:一般所指的placement new]

//一般所指的placement new
void* operator new(std::size_t, void* pMemory) throw();  

//防止構(gòu)造函數(shù)期間的內(nèi)存泄漏
class Widget
{
public:
    ...
    //一個(gè)placement new
    static void* operator new(std::size_t size, std::ostream& logStream)
        throw(std::bad_alloc);
    //提供placement delete,防止operator new成功,構(gòu)造函數(shù)失敗時(shí)造成的內(nèi)存泄漏
    static void* operator delete(std::size_t size, std::ostream& logStream)
        throw();
    //operator new正常運(yùn)行時(shí)調(diào)用的delete
    static void* operator delete(void* pMemory, std::size_t size)
        throw();
};

//以下語句如果引發(fā)Widget構(gòu)造函數(shù)拋出異常,對(duì)應(yīng)的placement delete會(huì)被調(diào)用,不會(huì)發(fā)生內(nèi)存泄漏
Widget* pw = new (std::cerr) Widget;

//如果沒有拋出異常,客戶端有如下代碼,則會(huì)調(diào)用正常的delete
delete pw;

//發(fā)生函數(shù)隱藏
class Base
{
public:
    ...
    static void* operator new(std::size_t size, std::ostream& logStream)
        throw (std::bad_alloc);   //會(huì)掩蓋正常的global形式
    ...
};

Base* pb = new Base;   //錯(cuò)誤!因?yàn)檎5膐perator new被掩蓋
Base* pb = new (std::cerr) Base;   //正確,調(diào)用Base的placement new

//繼承類掩蓋全局與基類的new
class Derived : public Base
{
public:
    ...
    //重新聲明正常形式的new
    static void* operator new(std::size_t size) throw (std::bad_alloc);
    ...
};

Derived* pb = new (std::clog) Derived; //錯(cuò)誤!因?yàn)楸谎谏w了
Derived* pb = new Derived;  //正確,調(diào)用derived的new


//std中的operator new
void* operator new(std::size_t) throw(std::bad_alloc);  //normal new
void* operator new(std::size_t, void*) throw();         //placement new
void* operator new(std::size_t, const std::nothrow_t&) throw(); //nothrow new

//解決名稱掩蓋
//提供一個(gè)基類內(nèi)含所有的正常形式的new和delete, 
class StandardNewDeleteForms
{
public:
    //normal new/delete
    static void* operator new(std::size_t size) throw(std::bad_alloc)
    {
        return ::operator new(size); //調(diào)用正常的new
    }
    static void* operator delete(void* pMemory) throw()
    {
        return ::operator delete(pMemory); //調(diào)用正常的delete
    }

    //placement new/delete
    void* operator new(std::size_t size, void* ptr) throw()
    {
        return ::operator new(size, ptr);
    }
    void* operator delete(void* pMemory, void* ptr) throw()
    {
        return ::operator delete(pMemory, ptr);
    }

    //nothrow new/delete
    void* operator new(std::size_t size, const std::nothrow_t& nt) throw()
    {
        return ::operator new(size, nt)
    }
    void* operator delete(void* pMemory, const std::nothrow_t& nt) throw()
    {
        return ::operator new(pMemroy);
    }
};

//如果想以自定義形式擴(kuò)充標(biāo)準(zhǔn)形式,則可利用繼承機(jī)制及using聲明式取得標(biāo)準(zhǔn)形式
class Widget : public StandardNewDeleteForms
{
public:
    using StandardNewDeleteForms::operator new;  //使這些形式可見
    using StandardNewDeleteForms::operator delete;

    //添加自定義的placement new/delete,這樣就可以在這些函數(shù)中調(diào)用標(biāo)準(zhǔn)形式的了
    static void* operator new(std::size_t size, std::ostream& logStream)
        throw(std::bad_alloc);
    static void* operator delete(std::size_t size, std::ostream& logStream)
        throw();
};

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容