条款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:
其中还有一个operator*
:
class Rational{
public:
const Rational operator* (const Rational& rhs) const;
};
Rational oneHalf(1,2);
Rational result = oneHalf * 2; //成功!
result = 2 * onewHalf; //失败
当用函数形式重写 问题便一目了然
result = oneHalf.operator*(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
,是为了给出很多选择,让编译器自行去根据合适度进行匹配,而不是固定调用!