TITLE: Value versus reference semantics in C++ [ A few definitions of terms might help make this more understandable: - An ADT class is one designed to be used by value rather than polymorphically. An example is a complex number class. - Slicing refers to the problem encountered when passing a derived class by value to a function expecting a base class. The extra state added by the derived class is sliced off, ie, lost. - State, as discussed below, covers argument types, return types, and data members. - This discussion is related to the notions of covariance and contravariance. If you completely understand this stuff, come by and explain it to me. :-) -adc ] PROBLEM: maxtal@physics.su.OZ.AU (John Max Skaller) In general, for polymorphic objects, slicing must *ADD* extra potential states. That is, you can only slice off constraints. You can slice away *non-essential* extra state, that is, state that cannot be accessed polymorphically. Well, this again re-inforces my concept that value oriented semantics and object oriented semantics work in exactly the opposite directions. RESPONSE: rmartin@uunet.uu.net!rcm (Robert Martin), 15 May 93 [a] At last, at last, I finally understand what you are getting at! And I agree. value semantics and reference semantics are fundementally different in C++. I see now what you meant when you said that value oriented derived classes must be more constrained than their base classes. That they can have no behavior altering states. And yet, classes which are polymorphic by reference are exactly the opposite. Derived classes must be less constrained than their base classes, and news states can be added that alter the behavior. [b] Interesting. My primary difficulty in understanding your point was that I so seldom use value semantics in C++. I only use value semantics on the most primitive of types. Things like "time" and "date" and the like. [c] Value classes can be made to behave polymorphically, and slicing can be avoided, if the value class is wrapped in an envelope. Users of the value class use it through the envelope, and the envelope quietly accesses the value class by reference. [d] This was the point that Jim Kanze was getting at in the operator= thread. Classes that are polymorphic in nature can be wrapped in envelope classes in order to give them value semantics. Coplien's book is a good source of information on this topic. RESPONSE: maxtal@physics.su.OZ.AU (John Max Skaller), 16 May 93 [a] Well, that is interesting, because you are still interpreting it differently from me. That doesnt mean *either* of us is wrong, mind you .. this stuff is hairy :-) I think that subclasses (polymorphic derived classes) have to be *more* constrained that their bases and mustn't add any new state .. opposite to what you said. For example: class Integer { public: virtual int Get()const =0; }; class MyInteger : public virtual Integer { int x; public: int Get()const { return x; } MyInteger(int i) : x(abs(i)) {} }; MyInteger is a subclass of Integer. There is no new *public* state involved: you have to count a pure virtual function such as 'Integer::Get()const' as representing 'int' states. MyInteger::Get, however, is *more constrained*: it can only return a non-negative integer. Now what of ADT classes? Can we 'add functionality' by inheritance? Well, you can, if you dont mind it being sliced off. I think the point is that *adding state* is composition. Not subclassing, which subtracts state. Mathematically, a subclass is a subset, whereas by composition one creates equivalence classes: that is, an 'object hierarchy'. Here's some composition: class X { public: int x; }; class Y : public X { public: int y; }; The set of Y objects which have the same 'x' value form an equivalence class. Normally in mathematics, one works with 'canonical representatives' of equivalance classes, that is, you pick an element of the class to represent the whole class. But here we dont need to do that: we can work *directly* with the equivalence class: the object of type X with the fixed 'x' value in question *is* the equivalence class of Y objects with that value of 'x'. I think you can see that in an object hierarchy, you can go on adding state ad infinitum: each new state variable in a derived class generates a set of equivalence classes which *are* the objects of the base class. But this is composition, not a smidgen of subclassing is involved here. Thus, object hierachies, and adding state or 'specialisation' or whatever are *not* subclassing, this technique is the exact opposite in C++ and cannot involve polymorphism. The interesting thing is that this is what Smalltalk style OO is all about, yet in C++ this thing that is the very core Object Oriented concept of Smalltalk is not Object Oriented at all: the classes are not subclasses, and polymorphism cannot be used: you have to downcast and use RTTI to make it work, emulating the dynamism that is central to Smalltalk OO. So what *is* subclassing used for in C++? For *implementing* abstractions. Nothing else. An implementation naturally constrains things: it fixes something of the abstraction in order to represent it. The resulting subclass, however, is not a new type: it is merely an implementation of an existing type (the abstraction). So you should hide the subclass and never access it as a subclass. Dont know if this helps. It sure seems contrary to the popular view of how to write classes in Object Oriented languages. [b] You still dont seem to agree, however. My suspicion is that you dont consider the return types of pure virtual functions as 'state'. If you do that, then you can see that any implementation of that virtual function must be a constraint .. a subset of what the pure virtual coudl return. Of course, it is often an *improper* subset (i.e. the same set) [c] Right. This technique (delegation) seems to be the correct way to support 'Object Oriented' ADT classes. [d] Yes. And I suspect that mixins do this too, but to a lesser extent. I have often talked about *factoring* designs. Separating the interface from the implementation. Separating the abstraction from the implementation. Separating the subclassing component of inheritance from the composiational component. Now, you're suggesting, in effect, that one must also separate the 'ADT/value oriented' component and the 'Object/reference semantics' component (by using 'delegation' as per Coplien). I think this feels right. If this turns out to work properly, then it should be *explicitly* supported by the language because it will be, in itself, a *fundamental* mechanism.