Effective C++笔记: 设计与声明(一)
2009-07-15 11:34
218 查看
Item 18: 使接口易于正确使用,不易被误用
假设一个用来表现日期的class设计构造函数:
class Date {
public:
Date(int month, int day, int year);
...
};
就可能发生以下错误:
Date d(30, 3, 1995); // Oops! Should be "3, 30" , not "30, 3"
第二,他们可能传递一个非法的代表月或日的数字:
Date d(2, 20, 1995); // Oops! Should be "3, 30" , not "2, 20"
我们可以引入简单的包装类型来区别日,月和年,并将这些类型用于 Data 的构造函数。
struct Day { struct Month { struct Year {
explicit Day(int d) explicit Month(int m) explicit Year(int y)
:val(d) {} :val(m) {} :val(y){}
int val; int val; int val;
}; }; };
class Date {
public:
Date(const Month& m, const Day& d, const Year& y);
...
};
Date d(30, 3, 1995); // error! wrong types
Date d(Day(30), Month(3), Year(1995)); // error! wrong types
Date d(Month(3), Day(30), Year(1995)); // okay, types are correct
更进一步,限制Month的值只能从1到12。做到这一点的一种方法是用一个枚举来表现月,但是枚举不像我们希望的那样是类型安全(type-safe)的。例如,枚举能被作为整数使用(参见 Item 2)。一个安全的解决方案是预先确定合法的 Month 的集合:
class Month {
public:
static Month Jan() { return Month(1); } // functions returning all valid
static Month Feb() { return Month(2); } // Month values; see below for
... // why these are functions, not
static Month Dec() { return Month(12); } // objects
... // other member functions
private:
explicit Month(int m); // prevent creation of new
// Month values
... // month-specific data
};
Date d(Month::Mar(), Day(30), Year(1995));
除此之外,限制类型内什么事可以做,什么事不能做。施加限制的一个普通方法就是加上 const。例如,Item 3 解释了使 operator* 的返回类型具有 const 资格是如何能够防止客户对用户自定义类型犯下这样的错误:
if (a * b = c) ... // oops, meant to do a comparison!
基本上,满足该条款的最好方法是尽量让你的新类型的表现和内建类型(如ints)保持一致!!
提供行为一致的接口。STL 容器的接口在很大程度上(虽然并不完美)是一致的,而且这使得它们相当易于使用。例如,每一种 STL 容器都有一个名为 size 的成员函数可以知道容器中有多少对象。与此对比的是 Java,在那里你对数组使用 length 属性,对 String 使用 length 方法,而对 List 却要使用 size 方法,
利用tr1::shared_ptr来保证资源的释放;
总结:
好的接口易于正确使用,而难以错误使用。你应该在你的所有接口中为这个特性努力。
使易于正确使用的方法包括在接口和行为兼容性上与内建类型保持一致。
预防错误的方法包括创建新的类型,限定类型的操作,约束对象的值,以及消除客户的资源管理职责。
tr1::shared_ptr 支持自定义 deleter。这可以防止 cross-DLL 问题,能用于自动解锁互斥体(参见 Item 14)等。
Item 20: 用 pass-by-reference-to-const(传引用给 const)取代 pass-by-value(传值)
Pass by reference能避免多余的构造函数和析构函数的开销;
为什么要const?将它声明为 const 是必要的,否则调用者必然担心传入的参数被改变。
以reference方式传递参数也可以避免slicing(对象切割)问题。示例:
class Window {
public:
...
std::string name() const; // return name of window
virtual void display() const; // draw window and contents
};
class WindowWithScrollBars: public Window {
public:
...
virtual void display() const;
};
现在,假设你想写一个函数打印出一个窗口的名字,并随后显示这个窗口。以下这个函数的写法是错误的:
void printNameAndDisplay(Window w) // incorrect! parameter
{ // may be sliced!
std::cout << w.name();
w.display();
}
考虑当你用一个 WindowWithScrollBars 对象调用这个函数时会发生什么:
WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb); // slicing happened
绕过切断问题的方法就是以传引用给 const 的方式传递 w:
void printNameAndDisplay(const Window& w) // fine, parameter won't
{ // be sliced
std::cout << w.name();
w.display();
}
如果你掀开编译器的盖头偷看一下,你会发现用指针实现引用是非常典型的做法,所以以引用传递某物实际上通常意味着传递一个指针。由此可以得出结论,如果你有一个内建类型的对象(例如,一个 int),以传值方式传递它常常比传引用方式更高效。那么,对于内建类型,当你需要在传值和传引用给 const 之间做一个选择时,没有道理不选择传值。同样的建议也适用于 STL 中的迭代器(iterators)和函数对象(function objects),因为,作为惯例,它们就是为传值设计的。迭代器(iterators)和函数对象(function objects)的实现有责任保证拷贝的高效并且不受切断问题的影响。(这是一个“规则如何变化,依赖于你使用 C++ 的哪一个部分”的实例——参见 Item 1。)
总结:
尽量以pass-by-reference-to-const代替pass-by-value, 高效且可以避免切割问题
这条规则并不适用于内建类型,及 STL 中的迭代器和函数对象类。对于它们,传值通常更合适。
相关文章推荐
- Effective C++笔记04:设计与声明
- Effective C++笔记:设计与声明
- Effective c++(笔记) 之 类与函数的设计声明中常遇到的问题
- Effective C++笔记: 设计与声明(二)
- Effective C++笔记: 设计与声明(三)
- Effective C++笔记: 设计与声明(四)
- Effective C++笔记(四):设计与声明
- 《More Effective C++ 35个改善编程与设计的有效方法》——第一章笔记
- Effective C++ 笔记 第四部分 设计与声明
- Effective C++笔记之一:声明、定义、初始化与赋值
- [Effective JavaScript 笔记]第15条:当心局部块函数声明笨拙的作用域
- <<Effective C++>>读书笔记4: 设计与声明
- effective C++笔记之条款11: 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
- 第四章 类和函数:设计与声明(Effective C++ Second Edition 读书笔记)
- Item 5:那些被C++默默地声明和调用的函数 Effective C++笔记
- Effective C++摘要《第4章:类和函数:设计与声明》20090209
- EffectiveC++第四章类和函数:设计与声明学习笔记
- Effective C++(四)接口设计与声明
- 《More Effective C++ 35个改善编程与设计的有效方法》——第二章笔记
- Effective C++学习笔记:初始化列表中成员列出的顺序和它们在类中声明的顺序相同