Effective C++ 笔记(6)

发布于 2023-08-09 12:42:54 字数 13193 浏览 34 评论 0

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 函数必须做以下事情:

  1. 让更多内存可被使用。这便造成 operator new 内的下一次内存分配动作可能成功。实现此策略的一个做法是,程序一开始执行就分配一大块内存,而后当 new-handler 第一次被调用,将它们释还给程序使用。
  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。

请记住:

  1. set_new_handler 允许客户指定一个函数,在内存分配无法获得满足时被调用。
  2. 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

文章
评论
84965 人气
更多

推荐作者

qq_aHcEbj

文章 0 评论 0

qq_ikhFfg

文章 0 评论 0

把昨日还给我

文章 0 评论 0

wj_zym

文章 0 评论 0

巴黎夜雨

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文