Effective C++(四)

所谓软件设计,是令软件做出你希望它做的事情的步骤和做法。本文中将主要介绍对良好C++接口的设计和声明。

  • 让接口容易被正确使用,不易被误用

欲开发一个“容易被正确使用,不易被误用”的接口,首先必须考虑客户可能做出什么样的错误。
另一个预防客户错误的方法是,限制类型内什么事可以做,什么事不能做。常见的限制是加上const。一旦在类的设计上有怀疑,就请拿ints做范本。避免无端与内置类型不兼容,真正的理由是为了提供行为一致的接口。
任何接口如果要求客户必须记得做某些事情,就是有着“不正确使用”的倾向。tr1::shared_ptr有一个特别好的性质是:它会自动使用它的“每个指针专属的删除器”,因而消除另一个潜在的客户错误:所谓的“cross-DLL problem”.这个问题发生于“对象在DLL中被new创建,却在另一个DLL内被delete销毁”。

请记住:

  • 阻止无用的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。
  • tr1::shared_ptr支持定制型删除器。这可以防范DLL问题,可以被用来自动解除互斥锁等。

  • 设计Class犹如设计type

当你定义一个新的class,也就是定义了一个新的type。重载函数和操作符,控制内存的分配和归还、定义对象的初始化和终结。全都在你手上。因此应该带着和“语言设计者当初设计语言内置类型时”一样的谨慎来研讨class的设计。

  • 新type的对象应该如何被创建和销毁?
  • 对象的初始化和对象的赋值该有什么差别?
  • 新type的对象如果被passed by value,意味着什么?
  • 什么是新type的“合法值”?
  • 你的新type需要配合某个集成图系嘛?
  • 你的新type需要什么样的转换?
  • 什么样的操作符和函数对此type而言是合理的?
  • 你的新type有多么一般化?

请记住:在定义一个新type之前,请确定你已经考虑过本条款所覆盖的所有讨论主题。


  • 宁以pass-by-reference-to-const替换pass-by-value
  • 效率高:不会有额外的构造函数和析构函数被调用,因为没有任何新的对象被创建
  • 避免切割问题,当一个derived class对象以pass by value方式传递并被视为base class对象,将产生切割问题。

如果深入C++编译器底层,你会发现,reference往往以指针实现出来,因此pass by reference通常意味真正传递的是指针。因此如果你有某个对象属于内置类型,pass by value往往比pass by reference 的效率高。原因如下:

  • 内置类型在按值传递时,只是将变量的值传递到栈上。调用函数将值取出,使用即可。
  • 按引用传参时,需要将被引用的变量的地址压栈,然后调用函数首先取出地址,然后根据地址取值。
  • 内置类型按值传参少了一个取址和寻址的过程。

一般而言,你可以合理假设“pass by value并不昂贵”的唯一对象就是内置类型和STL的迭代器和函数对象。


  • 考虑写出一个不抛异常的swap函数

swap是个有趣的函数,原本它只是STL的一部分,而后成为异常安全性编程的脊柱,以及用来处理自我赋值可能性的一个常见机制。

所谓swap两对象值,意思是将两对象的值彼此赋予对方。缺省情况下swap动作可由标准程序库提供的swap算法完成。

1
2
3
4
5
6
7
8
9
namespace std{
template<typename T>
void swap(T& a, T& b)
{
T temp(a);
a = b;
b = temp;
}
}

Tips:C++的名称查找法则(argument-dependent lookup或Koeing lookup):
当函数调用时,根据参数类型定义所在的命名空间或类域中查找函数名或运算符

请记住:

  • 当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常
  • 如果你提供一个member swap,也该提供一个non-member swap来调用前者。对于class(而非template),也请特化std::swap
  • 调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何命名空间资格修饰
  • 为用户定义类型进行std templates全特化是好的,但是千万不要尝试在std内加入某些对std而言是全新的东西。