Chapter 4. Designs and Declarations
什么样的接口是你应该提供给client的?或者,换句话说:什么样的接口是你希望被提供的?
If an attempted use of an interface won't do what the client expects, the code won't compile; and if the code does compile, it will do what the client wants.
如果你的programs不是写给你自己用,就应该实现上面的要求。
Item 18 - 25
条款18:Make interfaces easy to use correctly and hard to use incorrectly
想满足“易用难错”这样苛刻的要求,从编程的角度来看并不难。问题是随着你对接口及数据要求的增加,整个代码也将变得很冗繁,寻找到一个平衡点即可。
条款19:Treat class design as type design
How should objects of your new type be created and destroyed? 构造、析构、分配、释放。
How should object initialization differ from object assignment? 初始化 != 赋值,因为构造 != 赋值。
What does it mean for objects of your new type to be passed by value? 这基本意味着你要重写copy constructor。
What are the restrictions on legal values for your new type? 我们的codes在大多数情况下就是为了验证和处理约束条件及其引发的异常,这些工作应该在构造、赋值时被很好地完成。
Does your new type fit into an inheritance graph? 主要是要考虑virtual function和virtual destructor。
What kind of type conversions are allowed for your new type?
What operators and functions make sense for the new type? 成员、友元、静态?
What standard functions should be disallowed? 要么将其声明为private(不是万无一失的),要么就private继承一个Uncopable类。
Who should have access to the members of your new type? private、protected、public、friend?
What is the "undeclared interface" of your new type?
How general is your new type? 要想充分一般化,就用class template。
Is a new type really what you need? 冷静,看看GoF的DP。
条款20:Prefer pass-by-reference-to-const to pass-by-value
by value还是by reference的根本区别在于效率和安全。所谓效率体现在by reference避免了copy,而安全则体现在避免了slicing(对象切割)。将一个derived class iobject以by value传给base class object将发生slicing。
需要注意的是,对于build-in type、STL迭代器和函数对象最好还是用by value。
条款21:Don't try to return a reference when you must return an object
const Rational& rhs)
...{
Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
return result;
}
const Rational& operator*(const Rational& lhs, // warning! more bad
const Rational& rhs) // code!
...{
Rational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
return *result;
}
Rational w, x, y, z;
w = x * y * z; // same as operator*(operator*(x, y), z)
const Rational& operator*(const Rational& lhs, // warning! yet more
const Rational& rhs) // bad code!
...{
static Rational result; // static object to which a
// reference will be returned
result = ... ; // multiply lhs by rhs and put the
// product inside result
return result;
}
bool operator==(const Rational& lhs, // an operator==
const Rational& rhs); // for Rationals
Rational a, b, c, d;
...
if ((a * b) == (c * d)) ...{
do whatever's appropriate when the products are equal;
} else ...{
do whatever's appropriate when they're not;
}
if (operator==(operator*(a, b), operator*(c, d)))
// the right way
inline const Rational operator*(const Rational& lhs, const Rational& rhs)
...{
return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}
条款22:Declare data members private
我知道public不可取,但我觉得用protected对子类是一种方便。
条款23:Prefer non-member non-friend functions to member functions
自从开始用C++,我自己在写代码的时候,几乎再没用过non-member non-friend函数。有时候coding纯粹是coding,没有任何比较和规划,写哪儿是哪儿。Scott提醒了我:coding的时候要记得什么才是真正的OO。
条款24:Declare non-member functions when type conversions should apply to all parameters
If you need type conversions on all parameters to a function (including the one pointed to by the this pointer), the function must be a non-member.
记得在社区的一篇帖子里回答一个关于友元的问题的时候,我曾经这样说:
任何时候都最好不要用,包括ops。
所谓friend,就是可以帮你的忙,但是有代价:代价就是他了解了你的情况,can access your resources. 破坏了封装性,故称“白箱复用”。
如果实在不能通过自身的class实现功能,就用:Adaptor(具体参考GoF的<Design Patterns>).
当然,如果你觉得用Adaptor很麻烦,那么,在你想access你本来无法access的resources的时候,你就使你成为他的friend。
这就是它的好处,也是它的坏处。
条款25:Consider support for a non-throwing swap
template<typename T> // typical implementation of std::swap;
void swap(T& a, T& b) // swaps a's and b's values
...{
T temp(a);
a = b;
b = temp;
}
}
class Widget ...{ // same as above, except for the
public: // addition of the swap mem func
...
void swap(Widget& other)
...{
using std::swap; // the need for this declaration
// is explained later in this Item
swap(pImpl, other.pImpl); // to swap Widgets, swap their
} // pImpl pointers
...
};
namespace std ...{
template<> // revised specialization of
void swap<Widget>(Widget& a, // std::swap
Widget& b)
...{
a.swap(b); // to swap Widgets, call their
} // swap member function
}
namespace WidgetStuff ...{
... // templatized WidgetImpl, etc.
template<typename T> // as before, including the swap
class Widget ...{ ... }; // member function
...
template<typename T> // non-member swap function;
void swap(Widget<T>& a, // not part of the std namespace
Widget<T>& b)
...{
a.swap(b);
}
}
template<typename T>
void doSomething(T& obj1, T& obj2)
...{
using std::swap; // make std::swap available in this function
...
swap(obj1, obj2); // call the best swap for objects of type T
...
}