C++ Advanced - Note: parameter-independent 参数无关的代码抽离 templates;member function templates 运用成员函数模板接受所有兼容类型;请使用 traits class 表现类型信息;template metaprograming;new-handler;placement new,placement delete;不要轻忽编译器的警告;Boost,TR1。
- Created on 2014-05
factor parameter-independent code out of templates.
- Templates生成多个classes和多个函数,所以 任何template代码都不该与某个造成膨胀的template参数产生相依关系。
- 因非类型模板参数(non-type template parameters)而造成的代码膨胀, 往往可以消除,做法是以函数参数或class成员变量替换template参数。
- 因类型参数(type parameters)而造成的代码膨胀,往往可降低, 做法是让带有完全相同二进制表述(binary representations)的具现类型 (instantiation type)共享实现码。
template<typename T, std::size_t n> // n就是非类型参数
class SquareMatrix{
public:
void invert();
};
use member function templates to accept "all compatible types".
- 请使用member function templates(成员函数模板) 生成“可接受所有兼容类型”的函数。 2.如果你声明member templates用于“泛化copy构造”或“泛化ssignment操作”, 你还是需要声明正常的copy构造函数和copy assignment操作符。
template<typename T>
class SmartPtr{
public:
template<typename U>
SmartPtr(const SmartPtr<U>& other)
: heldPtr(other.get()) {
...
}
private:
T* heldPtr;
};
define non-member functions inside templates when type conversion are desired.
此条目相当复杂,所以最好查看原书。
当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数值之隐式类型转换”时, 请将那些函数定义为“class template内部的friend函数”。
use traits classes for information about types.
traits广泛应用于标准程序库。 但是,平时使用很少,导致看得不深入,难懂。 最好,看原书的条款。此处不详述太多。
如何设计并实现一个traits class:
- (1)确认若干你希望将来可取得的类型相关信息。如迭代器的种类。
- (2)为该信息选择一个名称。如iterator_category。
- (3)提供一个template和一组特化版本(如iterator_traits),内含你希望支持的类型相关信息。
// 对于5种迭代器种类,C++标准程序库分别提供专属的卷标结构(tag struct)
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参数
class deque{
public:
class iterator{
public:
typedef random_access_iterator_tag iterator_category;
...
};
};
template<...> // 略而未写的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;
...
}; // 它对用户自定义类型行得通,但对指针(另一种迭代器)行不通。
// 因为指针不可能嵌套typedef。
template<typename IterT>
struct iterator_traits<IterT*>{ // 利用偏特化解决,支持了指针迭代器
typedef random_access_iterator_tag iterator_category;
...
}
// advance函数用于移动迭代器
// 以下代码是,利用重载技术,在编译期对类型执行取代if...else测试
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag){
iter += d;
}
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::bidirectional_iterator_tag){...}
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::input_iterator_tag){...}
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d){
doAdvance(iter, d, typename std::iterator_traits<IterT>::iterator_category();
// 这里就不用if...else去测试用那种方式去移动迭代器了!
}
- Traits classes使得“类型相关信息”在编译期可用。 它们以templates和“templates特化”完成实现。
- 整合重载技术(overloading)后, traits classes有可能在编译期对类型执行if...else测试
be aware of template metaprograming.
- Template metaprograming(TMP,模板元编程)可将工作由运行期移往编译期, 因而得以实现早期错误侦测,和更高的执行效率!
- TMP可被用来生成“基于政策选择组合”(based on combinations of policy choices) 的客户定制代码,也可用来避免生成对某些特殊类型并不合适的代码。
TMP已被证明是个“图灵完全”(turing-complete)机器,意思是它的威力大到足以计算任何事物。
TMP没有循环构件,循环效果由递归完成recursion。 但其递归非递归函数调用,而是“递归模板具现化”(recursive template instantiation)。 如下的阶乘例子(factorial):
template<unsigned n>
struct Factorial{
enum { value = n * Factorial<n - 1>::value }; // enum hack
};
template<>
struct Factorial<0>{ // 模板特化
enum { value = 1 };
};
只要你指涉Factorial::value就可以得到n阶乘值。
为什么TMP值得学习?
- (1)确保度量单位正确。使用它,就可以确保(在编译期)程序中所有度量单位的组合都正确, 不论计算多复杂。所以它能用于早期侦测。
- (2)优化矩阵运算。
- 用高级与TMP相关的template技术,即expression templates,
- 就可能消除那些计算中临时产生的对象,并合并循环。
- 使其使用较少的内存,执行速度有大提升。
Matrix m1, m2, m3;
Matrix res = m1 * m2 * m3;
- (3)可以生成客户定制之设计模式(custom design pattern)实现品。
- 设计模式如Strategy、Ovserver、Visitor等等都可以多种方式实现出来。
- 运用policy-based design之TMP-based技术,可能产生一些templates用来表述的独立设计选项,
- 可任意组合他们,导致模式实现品带着客户定制的行为。
- 例如智能指针。
understand the behavior of the new-handler.
STL容器所使用的heap内存是由容器所拥有的分配器对象(allocator objects)管理, 不是被new和delete直接管理。
当operator new抛出异常以反映一个未获满足的内存需求之前, 它会先调用一个客户制定的错误处理函数,即new-handler。
namespace std{
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
} // 其返回值也是一个指针,指向set_new_handler被调用前正在执行
// (但马上就要被替换)的那个new-handler函数。
当operator new无法满足内存申请时,会不断调用new-handler函数函数,直到找到足够内存。
设计良好的new-handler必须做以下事情:
- (1)让更多内存可被使用。
- (2)安装另一个new-handler。 如果目前这个new-handler无法取得更多可用内存,或许它直到另外哪个new-handler有此能力。
- (3)卸载new-handler。 也就是将null指针传给set_new_handler。 一旦没有安装任何new-handler,operator new会在内存分配不成功时抛出异常。
- (4)抛出bad_alloc(或派生自bad_alloc)的异常。 这样的异常不会被operator new捕捉,因此会被传播到内存索求处。
- (5)不返回,通常调用abort或exit。
C++不支持class专属的new-handler,其实也不需要。 可令每个class提供自己的operator new和new-handler即可。
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 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);
}
// 1.调用set_new_handler,告知Widget的错误处理函数。
// 2.调用global operator new,执行实际的内存分配。
// 如果失败,operator new会调用Widget专属的new-handler。
// 若最终还是无法分配足够内存,会抛出bad_alloc异常。
// 然而之后,还要恢复原本的global new-handler,
// 然后再传播该异常。为了将原来的handler安装回去,
// 使用了资源管理对象,实例即是以下的NewHandlerHolder,防止资源泄漏。
// 3.global operator new能够分配足够的内存,operator new会返回一个指针,指向分配所得。
// Widget的析构函数会管理global new-handler,
// 自动将Widget's operator new被调用之前的那个new-handler恢复回来~!
template<typename T>
std::new_handler NewHolderSupport<T>::currentHandler = 0;
class NewHandlerHolder{
public:
explicit NewHandlerHolder(std::new_handler nh)
: handler(nh){}
~newHandlerHolder(){
std::set_new_handler(handler);
}
private:
std::new_handler handler;
NewHandlerHolder(const NewHandlerHolder&);
NewHandlerHolder& operator=(const NewHandlerHolder&);
};
- set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
- Nothrow new是一个颇为局限的工具,因为它只适用于内存分配;
后继的构造函数调用还是可能抛出异常。
understand when it makes sense to replace new and delete.
为什么要替换编译器提供的operator new&delete呢?
- (1)用来检测运用上的错误。
- 如,new的资源不小心delete掉时,导致内存泄露。
- 如,多次delete,导致了不确定行为。
- 这些都容易识别。但各式各样的编程错误导致
- overruns(写入点在分配区块之后)或underruns(写入点在分配区块起点之前)。
- 自定义的operator new就可以分配额外空间防止特定byte pattern(即签名,signatures),
- 检查分配区块在某生命时间点,是否发生了overruns或underruns。
- (2)为了强化效能。 定制版性能可以优化超过缺省版本。
- (3)为了收集使用上的统计数据。
- (4)为了增加分配和归还的速度。
- (5)为了降低缺省内存管理器带来的空间额外开销。 针对小型对象而开发的分配器(例如Boost的Pool程序库)本质上消除了这样的额外开销。
- (6)为了弥补却生分配其中的非最佳齐位(suboptimal alignment)。 如doubles的访问在x86体系结构上的访问是最快速的。
- (7)为了将相关对象成簇集中。
- (8)为了获得非传统的行为。
adhere to convention when writing new and delete.
详情最好自己重看原书。以下仅给出最后书中的简单概括:
- operator new应该内含一个无穷循环,
- 并在其中尝试分配内存,如果它无法满足内存需求,
- 就应该调用new-handler。它也应该有能力处理0 bytes申请。
- 专属版本则还应该处理”比正确大小更大的(错误)申请“。
- operator delete应该在收到null指针时不做任何事。
- class专属版本则还应该处理”比正确大小更大的(错误)申请“
write placement delete if you write placement new.
详情最好自己重看原书。以下仅给出最后书中的简单概括:
void* operator new(std::size_t) throw(std::bad_alloc); // normal new
void* operator new(std::size_t, void*) throw(); // placement new
// 从void*指针制定的位置开始分配内存
void* operator new(std::size_t, const std::nothrow_t&) throw(); // nothrow new
new和delete如果接受了额外参数,便称为placement的。 如果一个带额外参数的operator new没有”带相同额外参数“的对应版operator delete, 那么当new的内存分配动作需要取消并恢复旧观时就没有人员和operator delete会被调用。
- 当你写一个placement operator new,请确定也写出了对应的placement operator delete。 如果没有这样做,你的程序可能因为发生隐微而时断时续的内存泄露。
- 当你声明placement new和placement delete,请确定不要无意识(非故意)地掩盖了它们的正常版本。
pay attention to compilere warnings.
- 严肃对待编译器发出的警告信息。努力在你的编译器的最高(最严苛)警告级别下 争取”无任何警告“的荣誉。
- 不要过度倚赖编译器的报警能力,因为不同的编译器对待事情的态度并不相同。 一旦移植到另一编译器上,你原本倚赖的警告信息有可能消失。
familiarize yourself with the standard library, including TR1.
STL:容器、迭代器、算法、函数对象、容器适配器、函数对象适配器。 iostream:自定缓冲功能。 国际化支持:wchar_t。 数值处理:复数模板complex、纯数值数组valarray。 异常阶层体系:exception、logic_error、runtime_error…… C89标准程序库:……
智能指针:…… tr1::function、tr1::bind hash tables 正则表达式Regular expresstions tuples变量组 tr1::array、tr1::mem_fn、tr1::reference_wrapper 随机数random number 数学特殊函数 c99兼容扩充
Type traits:用以提供类型(types)的编译器信息。 tr1::result_of:是个template,用来推导函数调用的返回类型。
Familiarize yourself with Boost
Boost程序库对付的主题非常繁多,区分数十个类目,包括:
- 字符串与文本处理
- 容器
- 函数对象和高级编程(例如lambda)
- 泛型编程
- 模板元编程
- 数学和数值
- 正确性与测试
- 数据结构
- 语言间的支持
- 内存
- 杂项
- (1)Boost是一个社群,也是一个网站。
- 致力于免费、源码开放、同僚复审的C++程序库开发。
- Boost在C++标准化过程中扮演深具影响力的角色。
- (2)Boost提供许多TR1组件实现品,以及其它许多程序库。