| 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" |
| 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 |
| 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)); |
| if (a * b = c) ... // oops, meant to do a comparison! |
实际上,这仅仅是另一条使类型易于正确使用而难以错误使用的普遍方针的一种表现:除非你有很棒的理由,否则就让你的类型的行为与内建类型保持一致。客户已经知道像 int 这样的类型如何表现,所以你应该努力使你的类型的表现无论何时都同样合理。例如,如果 a 和 b 是 int,给 a*b 赋值是非法的。所以除非有一个非常棒理由脱离这种表现,否则,对你的类型来说这样做也应该是非法的。
避免和内建类型毫无理由的不相容的真正原因是为了提供行为一致的接口。很少有特性比一致性更易于引出易于使用的接口,也很少有特性比不一致性更易于引出令人郁闷的接口。STL 容器的接口在很大程度上(虽然并不完美)是一致的,而且这使得它们相当易于使用。例如,每一种 STL 容器都有一个名为 size 的成员函数可以知道容器中有多少对象。与此对比的是 Java,在那里你对数组使用 length 属性,对 String 使用 length 方法,而对 List 却要使用 size 方法,在 .net 中,Array 有一个名为 Length 的属性,而 ArrayList 却有一个名为 Count 的属性。一些开发人员认为集成开发环境(IDEs)能补偿这些琐细的矛盾,但他们错了。矛盾在开发者工作中强加的精神折磨是任何 IDE 都无法完全消除的。