条款18 让接口容易被正确使用,不易被误用

std::shared_ptr有一个特别好的性质:它会自动使用他的 ”每个指针专属的删除器“ ,因而消除另一个潜在的客户错误:cross-DLL problem。

这个问题发生于 对象在动态链接程序库中被new创建,却在另一个 DLL 内被 delete 销毁。会导致运行期错误 std::shared_ptr没有这个错误 因为它默认的删除器是来自它诞生的那个 DLL 的 delete。


Boost库中的shared_ptr是原始指针的两倍大,以动态分配内存作为簿记用途和 ”删除器之专属数据“ ,以virtual形式调用删除器,并在多线程程序修改引用次数时蒙受线程同步化的额外开销。(只要定义一个预处理器符号就可以关闭多线程支持)

他比原始指针大且慢,而且使用辅助动态内存 但是降低客户使用成本倒是真的!

条款19 设计class犹如设计type

新的类型对象如果被值传递会调用拷贝构造函数

如果希望允许类型T1被隐式转换为类型T2,就必须:
①在class T1中写一个类型转换函数operator T2
or ②在class T2中写一个 non-explicit-one-argument 构造函数,其参数为 T1

条款20 宁以pass-by-reference-to-const替换pass-by-value

默认情况下, C++以 by value 形式传递对象到函数,都是以实参的副本为初值,而调用端 return 的也是函数返回值的一个副本

  • 以 by-reference 方式传递参数也可以避免对象切割的问题

如果一个派生类对象以 by value 的方式传递并被视为一个基类对象,基类的构造函数会被调用派生类部分将被切割,只留下基类部分。


  • 在C++编译器底层,reference 往往是以指针实现出来的,因此 pass by reference 通常意味着真正传递的是指针。

以上规则不适用于内置类型,以及 STL 的迭代器和函数对象。对他们而言,pass by value 比较适当。

条款21 必须返回对象时,别妄想返回其reference

  • 任何一个函数如果返回一个 reference 指向某个 local 对象,都将一败涂地

任何时候reference都是一个别名,多想想它的另一个名字是什么东西

不要返回一个 pointer 或 reference 指向一个 local stack 对象,或返回 reference 指向一个 heap-allocated 对象,或 pointer 或 reference 指向一个 local static 对象而有可能同时需要多个这样的对象

在对象之间搬移数值的最直接办法是通过赋值操作,代价:调用一个析构函数加上一个析构函数 (销毁旧值,复制新值)

有时候 **operator*** 的返回值的构造和析构可被安全的消除

条款22 将成员变量声明为private

  • 如果public接口内全是函数,客户就不需要在打算访问class成员时迷惑是否使用小括号

将成员变量隐藏在函数接口的背后,可以为 “所有可能的实现” 提供弹性


假设有一个public成员变量,而我们最终取消了它,有多少代码可能会被破坏?因为public成员变量完全没有封装性。改动protected成员变量将会影响derived class。

  • 如果将一个成员变量声明为public或protected而客户开始使用它,就很难改变那个成员变量所涉及的一切,太多代码需要重写、重新测试、重新编译!
  • protected 并不比 public 更具有封装性。从封装角度而言,只有两种访问权限:private 和其他

条款23 宁以non-member、non-friend替换member函数

  • 越多函数可以访问它,它的封装性就越低

如果一个 成员函数非成员、非友元函数 之间做抉择,而且两者提供相同机能,那么导致较大封装性的是后者,因为它并不增加 “能够访问class内之private成分” 的函数数量。

条款24 如果一个函数的所有参数皆需类型转换,请将它写成 non-member 函数

假如你这样开始你的 Rational class:

image.png
其中还有一个operator*

  1. class Rational{
  2. public:
  3. const Rational operator* (const Rational& rhs) const;
  4. };
  1. Rational oneHalf(1,2);
  2. Rational result = oneHalf * 2; //成功!
  3. result = 2 * onewHalf; //失败

当用函数形式重写 问题便一目了然

  1. result = oneHalf.operator*(2);
  2. result = 2.operator*(oneHalf);
  • oneHalf 内含operator*成员函数,所以编译器调用该函数。并且发生了隐式类型转换。
  • 然而整数 2 没有这个成员函数。编译器会尝试调用 non-member operator*,但是不存在 。

只有当参数被列于参数列表中,这个参数才是隐式类型转换的合格参与者


operator*成为一个 non-member 函数,就允许编译器在每个实参身上执行隐式类型转换

如果需要为某个函数的所有参数(包括 this 指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是 non-member 的

条款25 考虑写出一个不抛异常的swap函数

  • 只要类型 T 支持拷贝(拷贝构造函数和拷贝赋值操作符)swap就可以完成任务

通常我们不允许改变std命名空间内的任何东西,但可以为标准templates制造特化版本,使它专属于我们自己的classes。

C++只允许对class templates偏特化,不能在function templates上偏特化

当打算偏特化一个 function template 时,惯常做法是为它添加一个重载版本(而不是企图特化它)

重载、全特化标准库的内容是被允许的,但是添加内容是不被允许的!


如果swap默认实现版效率不足(意味着你的class或template使用了pimpl手法),尝试:

①提供一个 public 的 swap 成员函数,在其内高效置换两个对象值

②在你的 class 或 template 所在命名空间内提供一个 non-member swap,并令它调用 **swap** 成员函数

③如果正在编写一个class(而不是一个class template),为你的 class 特化 **std::swap** ,并令它调用swap成员函数

HINT:如果你调用swap,请确定包含一个using声明式,让**std::swap**可见 此处不用std::swap,是为了给出很多选择,让编译器自行去根据合适度进行匹配,而不是固定调用!