我认为“值”与“对象”的区分在设计和实现时具有非常重要的指导作用, 因此下面做一些说明。 “值”与“对象”类型之间并没有严格定义的区分。但通常可以观察到下列不同: 0. “值”是“死的”、“傻的”、“简单的”、“具体的”、“可复制的”, “对象”是“活的”、“聪明的”、“复杂的”、“抽象的”、“不可复制的”。 这里的“复杂性”主要还是指行为的复杂性,而非结构的复杂性。例如, list< map< vector<string>, deque< set<int> > > > 仍然是一个不折不扣的“值”类型。 这里我们不在哲学的路上走太远,还是看看下面更具体的一些特征吧。 1. “值”的成员函数(包括解构函数)都不是 virtual 的,不是设计来被继承的。 “对象”的解构函数是 virtual 的,而且通常还有其它的 virtual 成员函数, 是设计来被继承的,或继承了其他基类。 // value: struct String { // non-virtual destructor: ~String() { delete[] s_; } private: char* s_; }; // string should not be public-ly inherited. // object: struct OutputDevice { virtual ~OutputDevice(); virtual void output( char const* ) = 0; }; struct ConsoleOutputDevice : OutputDevice { // inherits virtual destructor, // overrides virtual member function: void output( char const* ); }; 2. “对象”经常必须通过指针或引用来使用,“值”不一定需要。 ::std::auto_ptr<OutputDevice> output( new ConsoleOutputDevice() ); void f( OutputDevice& dev ) { // ... dev.output( "blah blah/n" ); // ... } String s = "dsjflsdjflsjlf"; String t = "djslfdsjfsl"; s += t; void g( String s ) // by-value is OK. { // ... } g( s ); 3. “值”可以复制出任意多份等价物,是 Assignable, CopyConstructible 的 (C++标准里定义了Assignable, CopyConstructible 的具体含义)。 “对象”通常不是 Assignable 也不是 CopyConstructible 的,通常是被共享的 不是被复制的。 即使“对象”被“克隆”,“克隆”出来的对象与原对象也不是 完全等价的:相同的基因,不同的个体。 string s = "dsjlfsdjlfsdjlfsdjl"; string t; t = s; // the following two lines are equivalent: cout << s; cout << t; auto_ptr<OutputDevice> dev1( new ConsoleOutputDevice(...) ); auto_ptr<OutputDevice> dev2( dev1.clone() ); // the following two lines are NOT equivalent: dev1->output( "blah blah" ); dev2->output( "blah blah" ); 这里,dev2 可能具有与 dev1 相同的位置、大小、权限等属性,但却是 完全不同的另一个窗口。 4. 对于“值”类型,应该严格保证 constness-correctness,对于“对象”类型, 通常不需要,甚至有些 'const' 是有害的。 // bad design of a value type: struct Contact { string address(); // non-const }; void f( Contact const& contact ) { cout << contact.address(); // won't compile. } // maybe bad design of an intelligent object type: struct Robot { string name( Object const& asker ) const // const! { return my_name; } private: string my_name; }; 由于 Robot 的智能,问他一次名字也可能要触发它内部状态的改变。 一般来说,我们不应该对一个复杂“对象”假设任何操作不改变其内部状态, 因此其界面上不应有 const 成员函数。 使用一个 const Robot 也是毫无意义的。如果你觉得 Robot 比较特殊,可以想想 const Database。 5. “值”是可以比较的,“对象”通常是不可比较的,要比较的话, 应该比较对象的地址,而不是内容。 根据“值”类型表达的概念的特点,比较关系运算可分为两个层次: a 相等性比较: bool operator == ( T const& ) const; bool operator != ( T const& ) const { return ! operator == ( other ); } 而且 operator !=( other ) 必须等价于 ! operator == ( other ), operator == () 必须是一个“等价关系”(equivalence relationship): 任给 T a, b, c: (自反的) (a == a) => true. (对称的) (a == b) => (b == a). (传递的) (a == b && b == c) => (a == c). 对于一个类型 T 的两个对象 a, b,如果表达式 (a == b) 可以转换为 bool 类型,而且这个 operator == () 是 T 上的一个等价关系,那么 就说 T 是 EqualityComparable 的。 b 排序关系比较: bool operator < ( T const& ) const; bool operator > ( T const& other ) const { return other < *this; } bool operator >= ( T const& other ) const { return ! operator < ( other ); } bool operator <= ( T const& other ) const { return ! operator > ( other ); } 这些运算之间的关系必须满足上面给出的实现所表达的等价性。 (因此通常只实现 operator<(),然后从这里 copy 另外三个!) operator <() 必须是 T 上的一个“严格弱序” (strict weak ordering)关系: 任给 T a, b, c: (irreflexive) (a < a) => false. (transitive) (a < b && b < c) => (a < c). (weak ordering) 定义 eqiv(a,b) = ! (a < b) && ! (b < a). equiv(a,b) && equiv(b,c) => eqiv(a,c). 这样,operator<() 可以在 equiv 决定的 T 的等价类上定义一个全序 (total ordering). 对于一个类型 T 的两个对象 a, b,如果表达式 (a < b) 可以转换为 bool 类型,而且这个 operator < () 是 T 上的一个严格弱序关系, 那么就说 T 是 LessThanComparable 的。 一个“值”类型可以只实现相等性比较。如果同时实现了排序关系比较, 那么必须有 (a == b) iff (! (a < b) && ! (b < a))。 上述对关系运算的要求来自于 C++ 标准中对容器类中对象的要求。即使 你的类型的对象不被放到标准容器中,也应该满足上述要求,否则就可能 产生一些让他人吃惊的行为。 综上所述,在设计一个类时,如果思考一下这个类表达的是一个简单的“值” 还是一个复杂的“对象”,非常有助于决定类的界面以及用法的许多方面: . 会不会被继承/有没有 virtual 函数? . 是否允许拷贝和赋值?还是必须共享/克隆? . 如何对待 constness-correctness? . 是否应该实现比较运算?实现哪些? 等等等等。 (除了“值”和“对象”的区分外,还有一批C++中的类型可以归类为 表达了一个“概念”,但那是另一个话题) "三月的外星人" <UFO@sina.com> 写入消息 news:a8g76p$cb14@svr.novel.com... > 标准中一些具体的类是否都应当看作是“值”类,比如“map",list,vector等? > 而一些抽象的类就可以看成是”对象“类? 标准的数据类型大致有以下几类: “值”: 各种基本类型:int, double, ...。各种指针。complex<>。 各种容器: vector<>, list<>, multi/set<>, multi/map<>, deque<>, bitset<>, basic_string<>, valarray<>... 以及它们的各种 iterator。 adaptors: stack<>, queue<>, priority_queue<>。 各种 functor: less, greater, equal_to, plus, minus, binder1st...。 locale 是把“对象”类型通过共享方式包装成“值”类型,以方便使用。 “对象”: iostream相关类:basic_ios<>, basic_*stream<>, basic_*streambuf<>。 各种facet: ctype_base/ctype<>, codecvt_base/codecvt<>...。 type_info。 “值与对象的混合物” 异常类:exception,bad_exception, logic_error, range_error...。 可以象“值”一样复制,但又有继承层次,可以通过基类(exception) 引用或指针来使用。这种设计在应用程序里最好避免,因为很容易引起 object-slicing: exception e = domain_error( "sqrt(NegativeNumber)" ); 上面一行能编译,能运行,但没有做想让它做的事。 “概念实现”:(象“值”一样简单,但不是用来装一个“值”的) iterator<>, input_iterator_tag, output_iterator_tag..., unary_function<>, binary_function<>...。 “特征描述” iterator_traits<>, char_traits<>, numeric_limits<>...。 “怪物”: auto_ptr<>。 |