Atela

导航

effective C++ 3rd 笔记(四)

模板与泛型编程条款41-48

条款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

posted on 2011-04-18 21:54  Atela  阅读(547)  评论(0编辑  收藏  举报