effective C++ 55 41-55筆記


條款41: 了解隱式接口和編譯期多態

對template參數而言,接口是隱式的implicit,奠基於有效表達式。多態則是通過template具現化和函數重載解析(function overloading resolution)發生於編譯器。

template<typename T>
void doProcessing(T& w) {
if (w.size() > 10 && w != someNastyWidget) {
T temp(w);
temp.normalize();
temp.swap(w);
}
}
//w要支持的接口由template中執行於w身上的操作來決定
//隱式接口:size,normalisze,swap,copy構造,operator!=
//operator>和operator!=有可能造成template具現化,以不同的template參數具現化function template會導致調用不同函數。編譯期多態。
//T必須支持size成員函數,但也可能從base class繼承而得,且不需返回數值類型,甚至不需要返回一個定義operator>的類型。
//唯一需要的是返回一個類型為X或可隱式轉換為X的對象,X加上int(10的類型)必須能夠調用一個operator>
//operator!=相同

顯式接口:函數簽名式

隱式接口:有效表達式,只要表達式成立(可隱式轉換),編譯期即可通過

條款42: 了解typename的雙重意義。

    聲明template參數時,前綴關鍵字class和typename可互換
    請使用關鍵字typename標識嵌套從屬類型名稱:但不得在base class lists或member initialization list內作為base class修飾符。

 

template<typename C>
void print2nd(const C& container) {
C::const_iterator * x; //x意圖作為一個指針
...
}
//const_iterator如果是C的static成員變量,或x是global變量名稱呢,表達式就是個operator*

template內出現的名稱如果相依於某個template參數,稱之為從屬名稱。如果從屬名稱在class內呈嵌套狀,我們稱它為嵌套從屬名稱(nested dependent name)。

c++規定: 如果解析器在template中遭遇一個嵌套從屬名稱,它便假設這個名稱不是個類型,除非你告訴它是。

template<typename C>
void print2nd(const C& container) {
if (container.size() >= 2) {
typename C::const_iterator iter(container.begin()); //typename顯示指定C::const_iterator是個類型
...
}
}
//任何時候當你想要在template中指涉一個嵌套從屬類型名稱就不許在緊鄰它的前一個位置加關鍵字typename


但此規則在base class lists或member initialization list中例外

template<typename T>
class Derived: public Base<T>::Nested { // base class list: 不允許typename
public:
explicit Derived(int x)
: Base<T>::Nested(x) { // mem init list: 不允許typename
typename Base<T>::Nested temp;
...
}
...
};

條款43:學習處理模板化基類內的名稱

//將信息傳給不同的公司,加密和不加密形式
class CompanyA {
public:
void sendCleartext(const std::string& msg);
void sendEncrypted(const std::string& msg);
...
};
class CompanyB {
public:
void sendCleartext(const std::string& msg);
void sendEncrypted(const std::string& msg);
...
};
class MsgInfo { ... }; // class for holding information

template<typename Company>
class MsgSender {
public:
... // ctors, dtor, etc.
void sendClear(const MsgInfo& info) {
std::string msg;
根據info產生信息msg
Company c;
c.sendCleartext(msg);
}
void sendSecret(const MsgInfo& info) // similar to sendClear, except
{ ... } // calls c.sendEncrypted

};

//然后我們想每次發送信息時志記log某些信息,derived合情合理
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
... // ctors, dtor, etc.
void sendClearMsg(const MsgInfo& info) {
將傳送前的信息寫至log
sendClear(info); //調用base class函數,編譯失敗,why?當base class被指定為MsgSender<companyz>時,sendClear不存在
將傳送后的信息寫至log
}
};

//如果有個CompanyZ堅持只使用加密方式發送信息
class CompanyZ {
public:
... //沒有sendCleartext函數
void sendEncrypted(const std::string& msg);
};
//所以一般性的MsgSender template對CompanyZ不再合適,所以使用全特化
template<>
class MsgSender<Companyz> {
public:
... //和一般template相同,只是刪掉了sendClear
void sendSecret(const MsgInfo& info)
{ ... }
};

//所以當LoggingMsgSender的base class被指定為MsgSender<Companyz>時,sendClear不存在,上面sendClear(info);那里編譯失敗
//編譯器知道base class templates有可能被特化,特化版本可能不提供和一般性template相同的接口,所以拒絕在模板化基類內尋找繼承來的名字

三種方法,讓C++不進入templatized base classes觀察的行為

//方法一: 在base class函數調用動作之前加上this->
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
void sendClearMsg(const MsgInfo& info){
write "before sending" info to the log;
this->sendClear(info); // 成立,假設sendClear將被繼承
write "after sending" info to the log;
}
};
//方法二: using聲明
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
using MsgSender<Company>::sendClear; //告訴編譯器,請它假設sendClear在base class內
void sendClearMsg(const MsgInfo& info){
...
sendClear(info); // 成立,假設sendClear將被繼承
...
}
};

//方法三: 明確指出被調用函數在base class內
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
void sendClearMsg(const MsgInfo& info){
...
MsgSender<Company>::sendClear(info); //成立,假設sendClear將被繼承
...
}
...
}; //但如果調用的是虛函數,會關閉virtual綁定行為


LoggingMsgSender<Companyz> zMsgSender;
MsgInfo msgData;
... // put info in msgData
zMsgSender.sendClearMsg(msgData); // error! 編譯失敗

條款44: 將與參數無關的代碼抽離templates

    Templates生成多個classes和函數,所以任何template代碼都不該與某個造成膨脹的template參數產生相依關系
    因非類型模板參數而造成的代碼膨脹,往往可消除,做法是以函數參數或class成員變量替換template參數
    因類型模板參數而造成的代碼膨脹,往往可降低,做法是讓帶有完全相同二進制表述的具體類型共享實現代碼

template<typename T, std::size_t n> // 非類型參數
class SquareMatrix {
public:
void invert(); // 求逆矩陣
};
SquareMatrix<double, 5> sm1;
sm1.invert(); // call SquareMatrix<double, 5>::invert
SquareMatrix<double, 10> sm2;
sm2.invert(); // call SquareMatrix<double, 10>::invert
//具現化兩份invert,除了階數n不同,兩個函數其他部分完全相同,引起代碼膨脹

//馬上想到可以把非類型形參抽離到函數參數的位置,
template<typename T>
class SquareMatrixBase { //與尺寸無關的base class
protected:
void invert(std::size_t matrixSize); // invert matrix of the given size
};
template<typename T, std::size_t n>
class SquareMatrix: private SquareMatrixBase<T> { //private繼承,is-implemented-in-terms-of
private:
using SquareMatrixBase<T>::invert; // 避免遮掩base版的invert。條款33
public:
void invert() { this->invert(n); } // 避免代碼重復,並隱式inline
}; // 為什么this-> ?條款43

//SquareMatrixBase::invert如何知道操作的數據在哪里? 可以增加一個指針指向數據地址。
//但如果這樣的函數很多,都使用這樣的參數似乎不好
//可以另SquareMatrixBase存儲一個指針指向數據地址,也可能會存儲尺寸
template<typename T>
class SquareMatrixBase {
protected:
SquareMatrixBase(std::size_t n, T *pMem) // store matrix size and a
: size(n), pData(pMem) {} // ptr to matrix values
void setDataPtr(T *ptr) { pData = ptr; } // reassign pData
private:
std::size_t size; // size of matrix
T *pData; // pointer to matrix values
};

template<typename T, std::size_t n>
class SquareMatrix: private SquareMatrixBase<t> {
public:
SquareMatrix() // send matrix size and
: SquareMatrixBase<T>(n, data) {} // data ptr to base class
...
private:
T data[n*n];
};

//使用動態分配內存
template<typename T, std::size_t n>
class SquareMatrix: private SquareMatrixBase<t> {
public:
SquareMatrix() // set base class data ptr to null,
: SquareMatrixBase<T>(n, 0), // allocate memory for matrix
pData(new T[n*n]) // values, save a ptr to the
{ this->setDataPtr(pData.get()); } // memory, and give a copy of it
... // to the base class
private:
boost::scoped_array<T> pData; // see Item 13 for info on
}; // boost::scoped_array

//所以現在不同尺寸的矩陣調用相同的base class函數,並且inline,解決代碼膨脹

但這樣做有代價,最早的代碼綁着尺寸的那個invert版本有可能生成比共享版本(尺寸以函數參數或存在對象內)更佳的代碼。如在尺寸專屬版中,尺寸是個編譯期常量,因此可以借由constant propagation來最優化,包括把它們折進被生成指令中成為直接操作數。這在與尺寸無關的版本中是無法辦到的。

但從另一角度看,不同尺寸的矩陣只擁有單一的invert,可減少執行文件大小,也因此降低程序的working set大小,並強化指令高速緩存區內的引用集中化(locality of reference)。這些都可能使程序執行更快,超越尺寸專屬版的invert的最優化效果。

到底哪個影響占主導??實際平台和數據。

working set指對一個在虛內存環境下執行的進程而言,其所使用的那一組內存頁。

這個條款只討論由非類型模板參數引起的膨脹,其實類型參數也會引起膨脹。如很多平台上int和long有相同的二進制表述,所以vector<int>和vector<long>可能完全相同,有些連接器會合並相同的函數實現碼,有些則不會。類似的,大多數平台上,所有指針類型有相同的二進制表述,因此凡template持有指針者如vector<int*> list<SquareMatrix<long,3>*>,往往應該對每一個成員函數使用唯一一份底層實現。這意味你實現某些成員函數而它們操作強型指針(strongly typed porinters,即T*)你應該另它們調用另一個操作無類型指針(void*)的函數,由后者完成實際工作。

條款45: 運用成員函數模板接受所有兼容類型

    使用menmer function templates生成可接受所有兼容類型的函數
    如果你聲明member templates用於泛化copy構造或泛化assignment操作,你還是需要聲明正常的copy構造函數和copy assignment操作符

class Top { ... };
class Middle: public Top { ... };
class Bottom: public Middle { ... };
template<typename T>
class SmartPtr {
public: // smart pointers are typically
explicit SmartPtr(T *realPtr); // initialized by built-in pointers
...
};
SmartPtr<Top> pt1 = SmartPtr<Middle>(new Middle);// 需要SmartPtr<Middle> 到SmartPtr<Top>的copy構造
SmartPtr<Top> pt2 = SmartPtr<Bottom>(new Bottom);// 需要SmartPtr<Bottom> 到SmartPtr<Top>的copy構造
//如果繼續增加派生層次,就必須要增加copy構造函數
class BelowBottom: public Bottom { ... };

//所以我們需要的是copy構造函數的模板
template<typename T>
class SmartPtr {
public:
template<typename U> // member template
SmartPtr(const SmartPtr<U>& other); // for a "generalized copy constructor"
... // 蓄意未聲明explicit,派生類指針到基類的隱式轉換
};
//我們不希望基類指針來構造派生類指針,不希望SmartPtr<Top>創建SmartPtr<Bottom>,因為沒有int*到double*的隱式轉換
template<typename T>
class SmartPtr {
public:
template<typename U>
SmartPtr(const SmartPtr<U>& other)
: heldPtr(other.get()) { ... } //只有當U*到T*的隱式轉換存在時菜通過編譯
T* get() const { return heldPtr; }
private:
T *heldPtr;
};

//TR1中關於tr1::shared_ptr的摘錄
template<class T> class shared_ptr {
public:
template<class Y> // construct from
explicit shared_ptr(Y * p); // any compatible
template<class Y> // built-in pointer,
shared_ptr(shared_ptr<Y> const& r); // shared_ptr,
template<class Y> // weak_ptr, or
explicit shared_ptr(weak_ptr<Y> const& r); // auto_ptr
template<class Y>
explicit shared_ptr(auto_ptr<Y>& r);
template<class Y> // assign from
shared_ptr& operator=(shared_ptr<Y> const& r); // any compatible
template<class Y> // shared_ptr or
shared_ptr& operator=(auto_ptr<Y>& r); // auto_ptr
...
};
//除了泛化copy構造函數,其他構造函數都為explicit,意味從某個shared_ptr類型隱式轉換為另一個是允許的
//auto_ptr並未被聲明為const,auto_ptr復制其實被改變了。
//聲明泛化copy構造函數,並不阻止編譯器生成合成的copy構造函數,一個non-template
//所以最好顯示聲明它 shared_ptr(shared_ptr const& r);

條款46: 需要類型轉換時請為模板定義非成員函數

當我們編寫一個class template,而它所提供之與此template相關的函數支持所有參數之隱式類型轉換時,將那些函數定義為class template內部的friend 函數。

 

template<typename T>
class Rational {
public:
Rational(const T& numerator = 0, // see Item 20 for why params
const T& denominator = 1); // are now passed by reference
const T numerator() const; // see Item 28 for why return
const T denominator() const; // values are still passed by value,
... // Item 3 for why they're const
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs,
const Rational<T>& rhs)
{ ... }
Rational<int> oneHalf(1, 2);
Rational<int> result = oneHalf * 2; // error! won't compile,對比條款24,除了是template
//你期盼將int(2)隱式轉換為Rational<int>??
//template實參推導過程中從不將隱式類型轉換函數納入考慮


//解決方案:為了讓類型轉換發生在所有實參上,需要non-member函數(條款24)
//為了另這個函數自動具現化,我們要將它聲明在class內部,所以加上friend
template<typename T> class Rational; // declare
template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs, // 令operator* 隱式inline的沖擊最小
const Rational<T>& rhs);
template<typename T>
class Rational {
public:
Rational(const T& numerator = 0,
const T& denominator = 1):n(numerator),d(denominator){}
const T numerator() const{return n;}
const T denominator() const{return d;}
friend const Rational operator*(const Rational& lhs, const Rational& rhs) { //必須定義在內部,否則連接報錯
return doMultiply(lhs, rhs); //在class template內,template名稱可作為template和其參數的縮寫
}
private: T n,d;
};

template<typename T> // define
const Rational<T> doMultiply(const Rational<T>& lhs, // helper
const Rational<T>& rhs) // template in
{ // header file,
return Rational<T>(lhs.numerator() * rhs.numerator(), // if necessary
lhs.denominator() * rhs.denominator());
}

條款47:請使用traits classes表現類型信息

    建立一組重載函數或函數模板,彼此間的差異只在於各自的traits參數。令每個函數實現碼與其接受之traits信息相應和。
    建立一個控制函數或函數模板,它調用上訴那些勞工函數並傳遞traits class所需信息。

STL迭代器5種分類:

輸入迭代器、輸出迭代器: 只能向前一次讀或寫。istream_iterator ostream_iterator

前向(forward)迭代器: 支持輸入輸出迭代器的所有操作,並可多次讀寫

雙向(bidirectional)迭代器: 增加支持向后移動,set、multiset、map、mutimap、list

隨機訪問迭代器:可在常量時間內向前或向后移動任意距離,支持關系操作符。  vecotr、deque、string

//c++標准程序庫分別提供的專屬的卷標結構(tag struct)加以確認
//is-a關系
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag: public input_iterator_tag {};
struct bidirectional_iterator_tag: public forward_iterator_tag {};
struct random_access_iterator_tag: public bidirectional_iterator_tag {};

//現在實現一個template,將某個迭代器移動某個給定距離
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d) {
if (iter is a random access iterator) {//如何在編譯期取得iter類型信息?traits技術
iter += d; // use iterator arithmetic
} // for random access iters
else {
if (d >= 0) { while (d--) ++iter; } // use iterative calls to
else { while (d++) --iter; } // ++ or -- for other
} // iterator categories
}

//traits技術對內置類型和用戶自定義類型的表現一樣好,因為不能往指針里嵌套東西,所以類型內嵌套信息就不合適了
//所以類型的traits信息必須在類型自身之外,標准技術是把它放進一個template及其特化版本里
//這樣的templates在標准程序庫中有多個,其中針對迭代器的命名為iterator_traits
template < ... > // 參數略而未寫
class deque {
public:
class iterator {
public:
typedef random_access_iterator_tag iterator_category; //隨機
...
};
...
};
template < ... >
class list {
public:
class iterator {
public:
typedef bidirectional_iterator_tag iterator_category; //雙向
...
};
...
};
template<typename IterT>
struct iterator_traits {
typedef typename IterT::iterator_category iterator_category;
...
};
template<typename IterT> // 針對指針的偏特化版本
struct iterator_traits<IterT*> {
typedef random_access_iterator_tag iterator_category;
...
};

//現在實現advance
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d) {
if (typeid(typename std::iterator_traits<Itert>::iterator_category) ==
typeid(std::random_access_iterator_tag))
iter += d; // use iterator arithmetic
} // for random access iters
else {
if (d >= 0) { while (d--) ++iter; } // use iterative calls to
else { while (d++) --iter; } // ++ or -- for other
} // iterator categories
}
//這將需要在編譯期的工作推遲到了運行期,不僅浪費時間且造成可執行文件膨脹
//解決方案:重載函數
template<typename IterT, typename DistT> // use this impl for
void doAdvance(IterT& iter, DistT d, // random access iterators
std::random_access_iterator_tag) // 支持負數
{
iter += d;
}
template<typename IterT, typename DistT> // use this impl for
void doAdvance(IterT& iter, DistT d, // bidirectional iterators
std::bidirectional_iterator_tag) // 支持負數
{
if (d >= 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
template<typename IterT, typename DistT> // use this impl for
void doAdvance(IterT& iter, DistT d, // input iterators
std::input_iterator_tag)
{
if (d < 0 ) { //不支持負數
throw std::out_of_range("Negative distance"); // see below
}
while (d--) ++iter;
}

template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d){
doAdvance( // call the version
iter, d, // of doAdvance
typename // that is
std::iterator_traits<Itert>::iterator_category() // appropriate for
); // iter's iterator
} // category

一些常用traits classes: value_type, char_traits, numeric_limits,  is_fundamental<T>,  is_array<T>,  is_base_of<T1,T2>

條款48: 認識template元編程 (metaprogramming)

了解有TMP這個技術,應用遞歸模板具現化,enum hack,增加編譯期OK

使用TMP的C++程序: 較小的可執行文件、較短的運行期、較少的內存需求。將工作從運行期轉移至編譯器。

條款47的traits解法就是TMP,運行期typeid類型測試代碼會出現在可執行文件中,所以TMP更高效。

TMP應用遞歸模板具現化(recursive template instantiation)

template<unsigned n> // general case: the value of
struct Factorial { // Factorial<n> is n times the value
// of Factorial<n-1>
enum { value = n * Factorial<n-1>::value };
};
template<> // special case: the value of
struct Factorial<0> { // Factorial<0> is 1
enum { value = 1 };
};


條款49: 了解new-handler的行為

當operator new拋出異常以反映一個未獲滿足的內存需求之前,它會調用一個客戶指定的錯誤處理函數new-handler

namespace std {
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
}
//set_new_handler的參數是個函數指針,指向operator new無法分配足夠內存該被調用的函數
//返回的是被調用前舊的函數指針

operator new里有個無窮循環,退出循環的唯一方法是內存被成功分配,或new-handler不斷不調用

一個設計良好的new-handler必須做以下事情

    讓更多內存可被使用: 一個策略:程序一開始執行就分配一大塊內存,而后當new-handler第一次被調用,將它們釋還給程序使用
    安裝另一個new-handler
    卸除new-handler,把null傳給set_new_handler,之后operator new分配失敗會立即拋出異常
    拋出bad_alloc(或從其派生)的異常
    不返回: 通常調用abort或exit

//c++不支持class專屬之new-handler,但可以自己實現
class Widget {
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;
};
std::new_handler Widget::currentHandler = 0; //類外定義,除非整形const
std::new_handler Widget::set_new_handler(std::new_handler p) throw() {
std::new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}

class NewHandlerHolder {
public:
explicit NewHandlerHolder(std::new_handler nh) // acquire current
:handler(nh) {} // new-handler
~NewHandlerHolder() // release it
{ std::set_new_handler(handler); }
private:
std::new_handler handler; // remember it
NewHandlerHolder(const NewHandlerHolder&); // prevent copying
NewHandlerHolder& // (see Item 14)
operator=(const NewHandlerHolder&);
};
void * Widget::operator new(std::size_t size) throw(std::bad_alloc) {
NewHandlerHolder h(std::set_new_handler(currentHandler)); //以對象管理new-handler函數指針資源
return ::operator new(size); //如果global調用失敗,調用Widget的new-handler,最終還是無法分配足夠內存,拋出異常
} //然后Widget的operatornew 必須恢復原理的global new-handler,對象自動釋放恢復該資源

//為實現代碼重用,設計template base class,能讓派生類繼承"設定class專屬之new-handler的能力"
template<typename T> // "mixin-style" base class for
class NewHandlerSupport{ // class-specific set_new_handler
public: // support
static std::new_handler set_new_handler(std::new_handler p) throw();
static void * operator new(std::size_t size) throw(std::bad_alloc);
... // other versions of op. new —
// see Item 52
private:
static std::new_handler currentHandler;
};
template<typename T>
std::new_handler
NewHandlerSupport<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);
}
// this initializes each currentHandler to null
template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;
class Widget: public NewHandlerSupport<widget> {
... // as before, but without declarations for
}; // set_new_handler or operator new
//template中參數T根本沒用到,只是讓每個不同的class有不同的currentHandler

另一形式的operator new:nothrow,分配失敗便返回null行為。

class Widget { ... };
Widget *pw1 = new Widget; // 如果分配失敗拋出異常
if (pw1 == 0) ... // 這個測試一定失敗
Widget *pw2 =new (std::nothrow) Widget; // 分配失敗返回0
if (pw2 == 0) ... // 測試可能成功

//nothrow new只能保證operator new不會拋出異常,不保證new (std::nothrow) Widget這樣的表達式(構造函數)絕不拋異常
//因此沒有運用nothrow new的需要

條款50:了解new 和delete的合理替換時機

專屬定制版本替換編譯器提供的operator new和operator delete的理由和時機

    用來檢測運用上的錯誤: 可超額分配內存,額外空間放置特定的byte patterns(即簽名),operator delete檢測簽名是否未修改,檢測是否overruns(寫入點在分配區塊尾端之后)或underuns(起點之前)
    為了收集使用上的統計數據: 。。。
    為了強化效能: 編譯器自帶版本對每個人都提供適度的好,不對特定任何人有最佳表現(中庸之道)。具體如下
    為了增加分配和歸還的速度: 如果你寫的是單線程程序,而編譯器帶的內存管理器具備線程安全,則可以寫部具線程安全的分配器而大幅改善速度
    降低缺省內存管理器帶來的空間額外開銷: 泛用型內存管理器往往在每一個分配區塊上招引某些額外開銷
    為了彌補缺省分配器中的非最佳齊位
    為了將相關對象成簇集中: 將數據集中在盡可能少的內存頁上,將內存頁錯誤頻率降至最低
    為了獲得非傳統的行為: 分配和歸還共享內存,定制版內調用C API

//global operator new,檢測overruns或underuns
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);
void *pMem = malloc(realSize); // call malloc to get theactual
if (!pMem) throw bad_alloc(); // memory
*(static_cast<int>(pMem)) = signature;
*(reinterpret_cast<int>(static_cast<byte>(pMem)+realSize-sizeof(int))) = signature;
return static_cast<byte>(pMem) + sizeof(int);
}

這個設計有兩個問題:
一:operator new里應該有個無窮循環,反復調用new-handler
二: 未考慮齊位(alignment): C++要求所有operator返回的指針都有適當的齊位(取決於數據類型),malloc就是這樣要求下工作的,所以返回malloc的指針是安全的。而上面代碼返回的是malloc指針且偏移一個int大小,不能保證安全。

條款51: 編寫new 和delete時需固守常規

    operator new應該內含一個無窮循環,嘗試分配內存,如無法滿足就調用new-handler。應該能處理0-byte申請,class專屬版還應該處理比正確大小更大的(錯誤)申請
    operator delete應該在收到null時不做任何事.class專屬版本還應該處理比正確大小更大的(錯誤)申請

C++規定,即使客戶要求0bytes,operator new也得返回一個合法指針

//non-member偽代碼
void * operator new(std::size_t size) throw(std::bad_alloc){
using namespace std;
if (size == 0) { // 處理0-byte申請
size = 1; // 將它視為1-byte申請
}
while (true) {
嘗試分配 size bytes;
if (分配成功)
return (一個指針,指向分配的內存);
// 分配失敗,找出目前的new-handler函數
new_handler globalHandler = set_new_handler(0);
set_new_handler(globalHandler);

if (globalHandler) (*globalHandler)();
else throw std::bad_alloc();
}
}


定制型內存管理器是為了指針某特定class的對象分配行為提供最優化,而不是為了class的任何derived class
也就是說為class X設計的operator new,其行為只為大小剛好為sizeof(X)的對象而設計。

class Base {
public:
static void * operator new(std::size_t size) throw(std::bad_alloc);
...
};
class Derived: public Base // Derived doesn't declare operator new
{ ... };
Derived *p = new Derived; // calls Base::operator new!

//解決
void * Base::operator new(std::size_t size) throw(std::bad_alloc) {
if (size != sizeof(Base)) // if size is "wrong,"
return ::operator new(size); // have standard operator new handle the request
...
}
//已包括檢測0,因為sizeof(Base)必然非0,所以一定會被轉交到::operator new

你不能在Base::operator new內假設array的每個元素大小為sizeof(Base),因為可能由繼承被derived class調用,因此不能假設array元素個數是bytes(申請數)/sizeof(Base).

此外傳遞給operator new[]的size_t參數有可能比將被填以對象的內存數量更多,因為條款16說過,動態分配的arrays可能含有額外空間來存元素個數。

operator delete:只需要記住C++保證刪除null指針永遠安全

void operator delete(void *rawMemory) throw() {
if (rawMemory == 0) return; // do nothing if the null
// pointer is being deleted
deallocate the memory pointed to by rawMemory;
}

 

//member版本
class Base { // same as before, but now
public: // operator delete is declared
static void * operator new(std::size_t size) throw(std::bad_alloc);
static void operator delete(void *rawMemory, std::size_t size) throw();
...
};
void Base::operator delete(void *rawMemory, std::size_t size) throw() {
if (rawMemory == 0) return; // check for null pointer
if (size != sizeof(Base)) { // if size is "wrong,"
::operator delete(rawMemory); // have standard operator
return; // delete handle the request
}
deallocate the memory pointed to by rawMemory;
return;
}

如果即將被刪除的對象派生自某個base class而其遺漏virtual析構函數,那么C++傳給operator delete的size_t數值可能不正確

條款52: 寫了placement new也要寫placement delete

規則: 如果一個帶額外參數的operator new沒有帶相同額外參數的對應版本operator delete,那么當new內存分配動作需要取消並恢復舊觀時久沒有任何operator delete被調用因此內存泄露。

placement delete只在伴隨placement new調用而觸發的構造函數出現異常時才會調用,對指針delete絕不會導致placement delete。所以我們要提供兩個版本的delete,一個正常版本的operator delete(用於構造期間無異常)和placement delete(構造期間拋出異常,額外參數和operator new一樣)

Widget* pw = new Widget; //先調用operator new,然后default構造函數

如果operator new成功,構造函數拋出異常,內存分配所得必須取消並恢復舊觀,而pw未獲得內存指針賦值,所以恢復的責任就落到運行期系統身上。

運行期系統會調用operator new相應的operator delete版本。

//如果只使用正常形式的new和delete運行期系統可以找到對應的delete
void* operator new(std::size_t) throw(std::bad_alloc);//正常簽名
void operator delete(void *rawMemory) throw(); //global中的正常簽名
void operator delete(void *rawMemory, //class內的正常簽名
std::size_t size) throw();


如果operator new的參數除了一定有的size_t外還有其他,則稱為placement new

類似的,如果operator delete接受額外參數,則稱為placement delete

//用的最多的一個placement new,通常說placement new就指它
void* operator new(std::size_t, void *pMemory) throw(); // "placement new"
//c++標准庫中該placement new的用法,在以分配空間上創建對象,如vector
new (place_address) type
new (place_address) type (initializer-list) //place_address 必須是一個指針,而initializer-list 提供了(可能為空的)初始化列表

 

class Widget {
public:
static void* operator new(std::size_t size, std::ostream& logStream)
throw(std::bad_alloc);
static void operator delete(void *pMemory,std::size_t size) throw();
...
};
Widget *pw = new (std::cerr) Widget;
//Widget默認構造函數拋出異常,運行期系統會尋找參數個數和類型都與operator new相同的某個operator delete
//所以這里正確的operator delete應該是
void operator delete(void *, std::ostream&) throw();

最后小心名字遮掩,條款33,遮掩名字而非簽名

//缺省情況下C++載global提供的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, // nothrow new —
const std::nothrow_t&) throw(); // see Item 49
//如果你的class內聲明任何operator new,會遮掩上述這些形式。
//可以用::或using聲明來調用global版本

條款53: 不要輕忽編譯器的警告

條款54: 讓自己熟悉包括TR1在內的標准程序庫

C++98: STL, iostream,國際化支持(wchar_t,wstring),數值處理(complex,valarray),異常階層體系,C89標准程序庫

TR1組件:

智能指針: shared_ptr, weak_ptr(解決循環引用,不參與引用計算,可讓循環的一方為weak_ptr)

tr1::function tr1::bind

hash_tables:TR1::unordered_set, tr1::unordered_multiset, tr1::unordered_map; tr1::unordered_multimap

正則表達式

Tuples(變量組),可以有任意個數的對象

tr1::array tr1::mem_fn tr1::reference_wrapper

隨機數

數學特殊函數(Laguerre多項式,Bessel函數,完全橢圓積分等等)

C99兼容擴充

type traits

tr1::result_of

條款55: 讓自己熟悉Boost

【轉載】http://www.cnblogs.com/Atela/archive/2011/04/18/2020252.html


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com