News:

This week IPhone 15 Pro winner is karn
You can be too a winner! Become the top poster of the week and win valuable prizes.  More details are You are not allowed to view links. Register or Login 

Main Menu

c++ tips - Value versus reference semantics in C++

Started by ben2ong2, October 07, 2006, 04:10:02 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

ben2ong2

[ 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: You are not allowed to view links. Register or Login (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: You are not allowed to view links. Register or Login!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.



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: You are not allowed to view links. Register or Login (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'. <Whew>

   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.


   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.


You are not allowed to view links. Register or Login
You are not allowed to view links. Register or Login