Effective C++ 笔记(6)
45. 运用成员函数模板接受所有兼容类型
Use member function templates to accept all compatible types
template <typename T> class SmartPtr { public: // 泛化 copy 构造函数并未被声明为 explicit,那是蓄意的,因为原始指针类型之间的转换(例如从 // derived class 指针转为 base class 指针)是隐式转换,无需明白写出转型动作(cast) template <typename U> SmartPtr(const SmartPtr<U>& other) : heldPtr(other.get()) {} // 以 other 的 heldPtr 初始化 this 的 heldPtr T* get() const { return heldPtr; } private: T* heldPtr; // 这个 SmartPtr 持有的内置(原始)指针 }
在 class 内声明泛化 copy 构造函数(是个 member template)并不会阻止编译器生成它们自己的 copy 构造函数(一个 non-template),所以如果你想要控制 copy 构造的方方面面,你必须同时声明泛化 copy 构造函数和”正常的”copy 构造函数。相同规则也适用于赋值(assignment)操作。
请记住:
- 请使用 member function template(成员函数模板)生成”可接受所有兼容类型”的函数。
- 如果你声明 member templates 用于 泛化 copy 构造 或 泛化 assignment 操作,你还需要声明正常的 copy 构造函数和 copy assignment 操作符。
46. 需要类型转换时请为模板定义非成员函数
Define non-member functions inside templates when type conversions are desired
template <typename T> class Rational46; template <typename T> const Rational46<T> doMutiply(const Rational46<T>& lhs, const Rational46<T>& rhs); template <typename T> class Rational46 { public: Rational46(const T& numerator = 0, const T& denominator = 1) {} const T numerator() const { return (T)0; } const T denominator() const { return (T)0; } friend const Rational46 operator*(const Rational46& lhs, const Rational46& rhs) { // return Rational46(lhs.numerator() * rhs.numerator(), lhs.denominator() * // rhs.denominator()); // 令 friend 函数调用辅助函数 return doMultiply(lhs, rhs); } }; template <typename T> const Rational46<T> doMultiply(const Rational46<T>& lhs, const Rational46<T>& rhs) { return Rational46<T>(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator()); } int test_item_46() { Rational46<int> oneHalf(1, 2); Rational46<int> result = oneHalf * 2; return 0; }
template 实参推导过程中从不将隐式类型转换函数纳入考虑。
在一个 class template 内,template 名称可被用来作为”template 和其参数”的简略表达方式,所以在 Rational 内我们可以只写 Rational 而不必写 Rational。
请记住:当我们编写一个 class template,而它所提供之”与此 template 相关的”函数支持”所有参数之隐式类型转换”时,请将那些函数定义为 class template 内部的 friend 函数。
47. 请使用 traits classes 表现类型信息
Use traits classes for information about types
// 类型 IterT 的 iterator_category template <typename IterT> struct iterator_traits { typedef typename IterT::iterator_category iterator_category; }; // template 偏特化,针对内置指针 template <typename IterT> struct iterator_traits<IterT*> { typedef std::random_access_iterator_tag iterator_category; }; // 实现用于 random access 迭代器 template <typename IterT, typename DistT> void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag) { iter += d; } // 实现用于 bidirectional 迭代器 template <typename IterT, typename DistT> void doAdvance(IterT& iter, DistT d, std::bidirectional_iterator_tag) { if (d >= 0) { while (d--) ++iter; } else { while (d++) --iter; } } // 实现用于 input 迭代器 template <typename IterT, typename DistT> void doAdvance(IterT& iter, DistT d, std::input_iterator_tag) { if (d < 0) { throw std::out_of_range("Negative distance"); } while (d--) ++iter; } template <typename IterT, typename DistT> void advance(IterT& iter, DistT d) { doAdvance(iter, d, typename std::iterator_traits<IterT>::iterator_category()); }
std::advance 用来将某个迭代器移动某个给定距离。STL 共有 5 种迭代器分类,可参考:https://blog.csdn.net/fengbingchun/article/details/77985191
Traits 并不是 C++关键字或一个预先定义好的构件;它们是一种技术,也是一个 C++程序员共同遵守的协议。这个技术的要求之一是,它对内置(built-in)类型和用户自定义(user-defined)类型的表现必须一样好。类型的 traits 信息必须位于类型自身之外。标准技术是把它放进一个 template 及其一或多个特化版本中。这样的 templates 在标准程序库中有若干个,其中针对迭代器者被命名为 iterator_traits。
iterator_traits 是个 struct,习惯上 traits 总是被实现为 structs,但它们却又往往被称为 traits classes。iterator_traits 的运作方式是,针对每一个类型 IterT,在 struct iterator_tratis 内一定声明某个 typedef 名为 iterator_category。这个 typedef 用来确认 IterT 的迭代器分类。
设计并实现一个 traits class:(1).确认若干你希望将来可取得的类型相关信息。例如对迭代器而言,希望将来可取得其分类(category)。(2).为该信息选择一个名称(例如 iterator_category)。(3).提供一个 template 和一组特化版本(例如 iterator_traits),内含你希望支持的类型相关信息。
如何使用一个 traits class:(1).建立一组重载函数(身份像劳工)或函数模板(例如 doAdvance),彼此间的差异只在于各自的 traits 参数。令每个函数实现码与其接受之 traits 信息相应和。(2).建立一个控制函数(身份像工头)或函数模板(例如 advance),它调用上述那些”劳工函数”并传递 traits class 所提供的信息。
Traits 广泛用于标准程序库。如 iterator_traits,除了供应 iterator_category 还供应另四份迭代器相关信息(其中最有用的是 value_type)。此外还有 char_traits 用来保存字符类型的相关信息,以及 numeric_limits 用来保存数值类型的相关信息。
请记住:
- Traits classes 使得”类型相关信息”在编译期可用。它们以 templates 和”templates 特化”完成实现。
- 整合重载技术(overloading)后,traits classes 有可能在编译期对类型进行 if…else 测试。
48. 认识 template 元编程
Be aware of template metaprogramming
// TMP 的阶乘运算, 一般情况:Factorial<n>的值是 n 乘以 Factorial<n-1>的值 template <unsigned n> struct Factorial { enum { value = n * Factorial<n - 1>::value }; }; // 特殊情况:Factorial<0>的值是 1 template <> struct Factorial<0> { enum { value = 1 }; }; int test_item_48() { fprintf(stdout, "Factorial<5>::value: %d\n", Factorial<5>::value); fprintf(stdout, "Factorial<10>::value: %d\n", Factorial<10>::value); return 0; }
Template metaprogramming(TMP, 模板元编程)是编写 template-based C++程序并执行于编译期的过程。所谓 template metaprogram(模板元程序)是以 C++写成、执行于 C++编译期内的程序。
请记住:
- Template metaprogramming(TMP, 模板元编程)可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率。
- TMP 可被用来生成”基于政策选择组合”(base on combinations of policy choices)的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。
49. 了解 new-handler 的行为
Understand the behavior of the new-handler
// 当 operator new 无法分配足够内存时,该被调用的函数 void outOfMem() { std::cerr << "Unable to satisfy request for memory\n"; std::abort(); } class NewHandlerHolder { public: explicit NewHandlerHolder(std::new_handler nh) : handler(nh) {} // 取得目前的 new-handler ~NewHandlerHolder() // 释放它 { std::set_new_handler(handler); } private: std::new_handler handler; // 记录下来 NewHandlerHolder(const NewHandlerHolder&); // 阻止 copying NewHandlerHolder& operator=(const NewHandlerHolder&); }; // "mixin"风格的 base class,用以支持 class 专属的 set_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); } template <typename T> std::new_handler NewHandlerSupport<T>::currentHandler = NULL; class Widget49 : public NewHandlerSupport<Widget> {}; int test_item_49() { std::set_new_handler(outOfMem); int* pBigDataArray = new int[100000000L]; Widget49::set_new_handler( outOfMem); // 设定 outOfMem 为 Widget49 的 new-handling 函数 Widget49* pw1 = new Widget49; // 如果内存分配失败,调用 outOfMem std::string* ps = new std::string; // 如果内存分配失败,调用 global new-handling 函数 Widget49::set_new_handler(NULL); // 设定 Widget49 专属的 new-handling 函数为 NULL Widget49* pw2 = new Widget49; // 如果内存分配失败,立刻抛出异常(clalss // Widget49 并没有专属的 new-handling 函数) return 0; }
当 operator new 无法满足某一内存分配需求时,它会抛出异常。以前它会返回一个 null 指针。当 operator new 抛出异常以反映一个未获满足的内存需求之前,它会先调用一个客户指定的错误处理函数,一个所谓的 new-handler。
为了指定这个”用于处理内存不足”的函数,客户必须调用 set_new_handler,那是声明于的标准程序库函数。new-handler 是个 typedef,定义出一个指针指向函数,该函数没有参数也不返回任何东西。set_new_handler 则是 获得一个 new-handler 并返回一个 new-handler 的函数。set_new_handler 的参数是个指针,指向 operator new 无法分配足够内存时该被调用的函数。其返回值也是个指针,指向 set_new_handler 被调用前正在执行的那个 new-handler 函数。当 operator new 无法满足内存申请时,它会不断调用 new-handler 函数,直到找到足够内存。
设计良好的 new-handler 函数必须做以下事情:
- 让更多内存可被使用。这便造成 operator new 内的下一次内存分配动作可能成功。实现此策略的一个做法是,程序一开始执行就分配一大块内存,而后当 new-handler 第一次被调用,将它们释还给程序使用。
- 安装另一个 new-handler。如果目前这个 new-handler 无法取得更多可用内存,或许它知道另外哪个 new-handler 有此能力。
- 卸除 new-handler,也就是将 null 指针传给 set_new_handler。一旦没有安装任何 new-handler, operator new 会在内存分配不成功时抛出异常。
- 抛出 bad_alloc(或派生自 bad_alloc)的异常。这样的异常不会被 operator new 捕捉,因此会被传播到内存索求处。
- 不返回,通常调用 abort 或 exit。
请记住:
- set_new_handler 允许客户指定一个函数,在内存分配无法获得满足时被调用。
- nothow new 是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能抛出异常。
std::nothrow 介绍参考
50.了解 new 和 delete 的合理替换时机
Understand when it makes sense to replace new and delete
内存对齐介绍参考:https://blog.csdn.net/fengbingchun/article/details/81270326
重载 new 和 delete 介绍参考:https://blog.csdn.net/fengbingchun/article/details/78991749
请记住:有许多理由需要写个自定的 new 和 delete,包括改善效能、对 heap 运用错误进行调试、收集 heap 使用信息。
51. 编写 new 和 delete 时需固守常规
Adhere to convention when writing new and delete
C++保证”删除 null 指针永远安全”。
请记住:(1).operator new 应该包含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用 new-handler。它也应该有能力处理 0 btyes 申请。class 专属版本则还应该处理”比正确大小更大的(错误)申请”。(2).operator delete 应该在收到 null 指针时不做任何事。class 专属版本则还应该处理”比正确大小更大的(错误)申请”。
52. 写了 placement new 也要写 placement delete
Write placement delete if you write placement new
如果 operator new 接受的参数除了一定会有的那个 size_t 之外还有其它,这便是所谓的 placement new。”placement new”意味带任意额外参数的 new。类似于 new 的 placement 版本,operator delete 如果接受额外参数,便称为 placement delete。
如果一个带额外参数的 operator new 没有”带相同额外参数”的对应版 operator delete,那么当 new 的内存分配动作需要取消并恢复旧观时就没有任何 operator delete 会被调用。
请记住:
- 当你写一个 placement operator new,请确定也写出了对应的 placement operator delete。如果没有这样做,你的程序可能会发生隐微而时断时续的内存泄漏。
- 当你声明 placement new 和 placement delete,请确定不要无意识(非故意)地遮掩了它们的正常版本。
53.不要轻忽编译器的警告
Pay attention to compiler warnings
请记住:
- 严肃对待编译器发出的警告信息。努力在你的编译器的最高(最严苛)警告级别下争取”无任何警告”的荣誉。
- 不要过度依赖编译器的报警能力,因为不同的编译器对待事情的态度并不相同。一旦移植到另一个编译器上,你原本依赖的警告信息有可能消失。
54. 让自己熟悉包括 RT1 在内的标准程序库
Familiarize yourself with the standard library, including TR1
TR1 代表 Technical Report 1。TR1 自身只是一份规范,为获得 TR1 提供的好处,你需要一份实物,一个好的实物来源是 Boost。
- std::shared_ptr 介绍参考:https://blog.csdn.net/fengbingchun/article/details/52202007
- std::weak_ptr 介绍参考:https://blog.csdn.net/fengbingchun/article/details/52203825
- std::function 介绍参考:https://blog.csdn.net/fengbingchun/article/details/52562918
- std::bind 介绍参考:https://blog.csdn.net/fengbingchun/article/details/52613910
- std::unordered_map 介绍参考:https://blog.csdn.net/fengbingchun/article/details/52235026
- C++11 中的正则表达式介绍参考:https://blog.csdn.net/fengbingchun/article/details/54835571
- std::tuple 介绍参考:https://blog.csdn.net/fengbingchun/article/details/72835446
- std::array 介绍参考:https://blog.csdn.net/fengbingchun/article/details/72809699
55. 让自己熟悉 Boost
Familiarize yourself with Boost
请记住:(1).Boost 是一个社群,也是一个网站。致力于免费、源码开放、同僚复审的 C++程序库开发。Boost 在 C++标准化过程中扮演着深具影响力的角色。(2).Boost 提供许多 TR1 组件实现品,以及其它许多程序库。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论