《Effective C++》 third edtion, Scott Meyers, 侯捷译.
所谓软件设计,是“令软件作出你希望他做的事情”的步骤和方法。
Item18. 让接口容易被正确使用,不易被误用
理想上来说,如果客户企图以不正确的方式使用接口,这个代码就不应当通过编译。
/* bad design */
class Date {
public:
Date(int mouth, int day, int year);
...
};
上述代码存在两个问题:
- 可能传递的年月日三方面的参数不匹配;
- 可能传递不合法的月份或者年份
/* wrapper types */
struct Day{
explicit Day(int d) : val(d) {}
int val;
};
struct Month{
explicit Month(int m) : val(m) {}
int val;
};
struct Year{
explicit Year(int y) : val(y) {}
int val;
};
class Date{
public:
Date(const Month& m, const Day& d, const Year& y);
...
};
Date d(30, 3, 1995); // fail to compile
Date d(Day(30), Month(3), Year(1995)); // fail to compile
Date d(Month(3), Day(30), Year(1995));
明智而审慎地导入新类型,可以有效预防接口被误用。
下面再解决如何限制输入的值,
一年的年份只有12个月,所有Month也应当反映这件事情。办法之一是使用枚举类型,但是枚举类型并不拥有类型安全性,enum总是可以被隐式转化为int。比较安全的解法是预先定义所有有效的Month。
class Month {
public:
static Month Jan() {return Month(1);}
static Month Feb() {return Month(2);}
...
private:
explicit Month(int m);
...
};
Date d(Month::Jan(), Day(30), Year(1995));
- 使用静态成员函数
除非有好的理由,否则应该尽量保证定义的类型与内置的类型一致,STL容器的接口就十分的一致
Item19. 设计class犹如设计type
C++作为一个OOP语言,当定义一个class就是定义一个type,应当带着“语言设计者当初设计语言内置类型时”一样的谨慎来研讨class的设计。
- 新的type如何被创建和销毁(operator new, ner[], delete, delete[])
- 对象的初始化和赋值有什么区别(constructor function and assignment operator)
- 新的type如果被passed by value,意味着什么(copy constructor)
- 什么是新type的合法值
- 新的type需要配置继承图系(inheritance graph)
- 新的type需要怎样的类型转换
- 什么样的操作符和函数对此新type是合理的
- 什么样的标准函数应该被驳回(private or delete)
- 什么是新类型的未声明接口(undeclared interface)
- 新类型有多么一般化,是否需要定义为模板类型
- 真的需要一个新类型吗?是否可以通过现有类型的继承得到
Item20. 宁以pass by reference to const 替换pass by value
引用传递相较于值传递的优点有两方面:(引用底层的实现是指针)
- 节约了临时对象的构造和析构成本;
- 避免了派生对象的切割问题,派生对象在值传递过程中丢失了派生所特有的性质。
class Window{
public:
...
std::string name() const;
virtual void display() const;
};
class WindowWithScrollBars : public Window() {
public:
void display() override const;
};
// pass by value
void printNameAndDisplay(Window w) {
std::cout << w.name() << std::endl;
w.display(); // invoke Window::display, no matter what w is
}
// pass by reference
void printNameAndDisplay(const Window& w) {
std::cout << w.name() << std::endl;
w.display(); // invoke Window::display or WindowWithScrollBar::display, according to w
}
Item21. 必须返回对象时,别妄想返回其reference
绝不要返回一个指针或者引用指向一个local stack对象,或者返回一个reference指向一个heap-allocated 对象。
《Effective C++》 是本好书.