A question
One day I will get around to providing the answers to some problems. I feel a bit guilty at the moment as all I seem to do is ask for the advice of those more experienced than I, in the hope I can learn something.
This will be the third article I have written for C Vu/Overload and I will take my usual approach of posing a problem (or two) and hoping that somebody will answer it (them).
This particular problem is fairly easy to define and, I suspect, is a problem that a lot of people new to C++ have. Like me, I suspect they "program round it" and as a result get into an horrendous mess as the project develops.
But, I suspect, that the problem I am about to describe is fundamental to OO programming and unless you have an understanding of the concepts then you are likely to get into the mess that I am currently in.
Let us say we have an object which we'll call a box. The aim of this object is to allow you to draw a box on the screen and manipulate it in various ways. For example you can change the colour of the border, you can change the background colour, you can print text in it, you can enquire upon its size (width and depth) etc. etc.
The attributes of the box (such as co-ordinates, colour, text, text font, text size etc.) are all held within data members that are part of the class.
Member functions are used to retrieve this information and other member functions provide the facilities to draw the box, display text etc. etc.
So far so good
Now let us assume we have another class. This class, for the benefit of this example, draws a picture of a man on the screen (we'll assume we have created an object of man called Jack).
The data members of the "man" class define attributes like his size, his colour etc. etc.
Member functions would take care of drawing Jack, setting the colour and would also provide the class with the ability to tell the outside world about its attributes e.g. {return colour} .
Still looking good (at least I hope it is).
Now to the problem. What do you do if you want to draw Jack inside the "box"?
In essence how do you join the two classes together.
I see a couple of options.
You could do something like this.
Jack.drawman(box);
and in the drawman function prototype you would have
int man::drawman(Box&);
This passes by value the address of the box object to the drawman member function. The drawman function could now do this
x=box.returnx; y=box.returny;
It can now use the x and y co-ordinates to position the man on the screen.
In effect, by passing the man class the address of the box class, the man class is able to call the public member functions of the box class. This, at first sight, might seem like a good idea but it seems to link the man and box class together. This means the man and box object have to "know" about each other, even though they might not "need" each other.
What do you do for example, if you just wanted to draw Jack without putting him in the box. I suppose you could overload the drawman function so that "normal" x and y co-ordinates could be passed but it all seems a bit messy. Also, when compiling the man class, you would still need to have access to the box class which seems a bit wasteful.
Another option could be to do this
x=box.returnx; y=box.returny; Jack.drawman(x,y);
or (more simply?)
Jack.drawman(box.returnx,box.returny);
The above code calls the drawman member function and tells it the co-ordinates of the box. These co-ordinates could be used to tell the man where to draw himself.
This seems better but it still seems a bit messy. You are manipulating two objects in the "main" (or controlling code) when, if I understand OO correctly, you should be able to say to the man object "Go away and draw yourself and don't forget that you need to draw yourself within the confines of that box we talked about earlier."
Can you say something like this in C++? In essence allowing the man class to know about the box class when it needs to but, if you are just drawing the man on the screen directly, the man has no knowledge of a box class.
As I said at the beginning on this article you can "program round" this problem, and I have shown two possible ways, but I suspect there are hundreds of others but what is the proper C++/OO method?
Bear in mind that I have only described two classes being "linked." The project I was working on has nine boxes on the screen and I ended up passing all nine box addresses to most functions. You can't tell me that is good programming?
I suspect inheritance will come into it somewhere but how? Do you derive a class from man and box and somehow link the co-ordinates of both classes? And how would you use inheritance if you have many classes involved?
An answer from The Harpist
When Francis passed Graham's article to me, my first instinct was to write a short article for C Vu. The longer I thought about it the more it seemed to me that a fully satisfying answer is rather beyond the scope of the defined targets of C Vu. Overload seemed to be the right place so here is my first submission to C Vu's sibling publication.
The first problem we must tackle is at the design stage. Both 'jack' and the 'box' are objects but what kind of objects are they? The novice just sees them as simple objects and programs them in a static form. Note that in this context static means an object that is fully defined and unambiguously identified at compile time whereas I use dynamic to refer to objects whose full properties will only be determined at run time. I hope that by the end of this article you will be comfortable with this pair of concepts and will be better able to relate them to object-based (OBP) versus object-oriented programming (OOP).
Almost all programmers pass through a stage of confusing OBP with OOP. Each has its place, its advantages and its problems. To further confuse matters OOP objects can often be used as OBP ones though the reverse process always contains defects. To put it another way, your compiler can often determine that a specific access to a polymorphic object is unambiguously defined at compile time and so can use more efficient static access methods.
Look at 'Jack'. He is an instance of the class man. What sort of class is 'man'? Surely it isn't an isolated class? Is there a class 'woman'? Surely there must be, at least implicitly. And in a modern non-sexist you better not use 'man' as a base class for 'woman' however much biblical support you quote for such a view. Clearly there must be a parent (base) class 'person' which is used to derive two equal (but different) status classes 'man' and 'woman' (and if you don't think they are different you have a far more serious problem than distinguishing between OOP and OBP) Now if you design your classes so that you must always either know someone's gender at compile time or the action is gender neutral and generic 'person' behaviour will suffice you will be writing in an OBP style. If you design your classes so that the specific response to certain actions will be determined in at run time in the context of the gender of a person then you are likely to be considering your design from an OOP view.
Let me give you a small example of part of a class interface for the three classes 'person', 'man' and 'woman'.
class Person { // data PersonList * children; // null pointer // terminated list Person * mother; Person * father; // protected interface public: bool has_children() { return children ? true : false; } void is_parent() { if (has_children) { cout << "I am a parent"; } else { cout << "I am not a parent"; } } // other member functions }; class Man: public Person { // extended private interface // extensions to protected interface public: void is_parent() { if (has_children) { cout << "I am a father"; } else { cout << "I am not a parent"; } } // other member functions }; class Woman: public Person { // extended private interface // extensions to protected interface public: void is_parent() { if (has_children) { cout << "I am a mother"; } else { cout << "I am not a parent"; } } // other member functions };
Please note that has_children() is a kind of access function and its existence allows the new versions of is_parent() to work without the Person data (which is a pure implementation detail being made directly available - provide protected access functions where needed but do not provide direct access to data in a base class, otherwise you can never have second thoughts. Keep a clear view, the private interface encapsulates those things that are strictly to do with implementing a class, the protected interface is for implementation details that concern a class and its derived versions and a public interface is to enable others to use your class.)
The above hierarchy is an OBP one. For example consider the following:
int main(){ Man jack; Woman jill; Person * someone = &jack; jack.is_parent(); jill.is_parent(); someone->is_parent(); someone= &jill; someone->is_parent(); }
I know that I have left out scads of code. Without any constructors specified the exact output of this program is impossible to determine but you should be able to see that the compiler will not determine the true gender of someone (in this example it could, but we might select someone to point to jack or jill in response to some external run time event and then the compiler simply could not know). Make sure you understand this because unless you do you do not understand static versus dynamic binding.
Now if we want to upgrade this program to OO rather than OB we must declare the member function is_parent() virtual in the Person class. The effect of that small extra is to move the Person class into being polymorphic. The compiler can no longer determine what you wish to do at compile time (actually, a really good optimising compiler will recognise that it can in the code as written) and so must refer to a hidden pointer in the jack/jill objects that points to a lookup table of polymorphic (virtual) functions to find the address of the (in this case) first function for the appropriate derived class.
Note (and Microsoft might note this as well when they next revise MFC) that a polymorphic class should have a virtual destructor to ensure that resources are correctly de-allocated if a derived dynamic instance is being handled through a base class pointer.
Now let us get back to Graham's problem. A property of jack is that he can be in something. The problem is that we forgot to allow for this when we designed the original. If jack is simply an instance of a simple class Person we can quickly fix the problem by deriving a new version of Person that includes a pointer to a class Container of which box is an instance. But this is where the OBP view begins to creak. The pointer will be to a specific type, and unless it is a polymorphic type we will only be able to put jack in one kind of container.
If we change our viewpoint to the 'box' we will realise that it is a property of that kind of object that it can contain things (one or more). Again, in an OBP environment we have to decide precisely what sort of object box may contain.
As you see, we can fix the problem on a piecemeal basis, and we can even do so without touching the original code. That much inheritance will provide even in a purely OBP environment. It isn't really enough.
Two new classes
Consider the following:
class Contents { Container * wrap; // the rest of the class, polymorphic for // preference }; class Container { List_of_Contents * contents; // yes, you now need some mechanism to hold a // list of Contents // rest of class };
Note that you cannot avoid pre-declaring some of the classes needed for this scheme because you have a chicken and egg type problem. Also note that these are really stub type classes encapsulating a single property that may be shared by many different types of object.
A box has the property that it can both contain containable objects and be contained by container type objects so how should we define it. Let me take a first cut at the problem.
class Box { Contents c; Container w; // other details };
The problem with this aggregation approach is that it fails because this does not turn a Box into an object of type Container. To see the problem, let us revisit Person.
class Person_with_Container: public Person { Container * cp; // rest of class } jack;
But cp must point to a Container type of object and Box is not such a type. We must change Box so that it is a derived type of Container:
class Box: public Container { // other details };
Now jack.cp can point to a Box type object and as long as we have made the Container type polymorphic the actual container (a Box in this case) will behave correctly.
Trouble
The problem with this approach is that we have taken a very one-sided view of a Box, that of being a container. What happens when we want to put it in something else? Well to achieve that we must make sure that it is a Content type object so that it can belong to a List_of_Contents type object. That means it must be derived from the Contents class. So now we must write:
class Box: public Container, public Contents { // class details };
Multiple inheritance
It is for this kind of programming style that multiple inheritance was designed. I know that you can manage without but once you start encapsulating properties to provide building blocks for less abstract objects, multiple inheritance becomes increasingly attractive. In a polymorphic environment aggregation (or layering as it is sometimes called) is not enough. If an object needs to be viewed as many different types of thing simultaneously then it must be derived from many different types of things.
Fix-ups
One problem of OOP is that you do need to get the design right to start with. Look back at our Person hierarchy. To insert the property that a person can be a content we have to provide the modification at that level, it is too late to do so at the 'Man' or 'Woman' stage. There is no mechanism for splicing properties into an inheritance graph, for that you really do need the original source code and a recompilation of the class library. It is no good coming along to Francis with bright ideas as how to extend C++ to incorporate such a feature, it is too late. Having said that, there are, of course, programming mechanisms developed by experts which allow modification to base classes but not quite to the extent of adding in another polymorphic type. If this is the sort of thing that challenges you, go back and read Francis' article on the Cheshire Cat. Now imagine that you have a pointer to a Suitcase object that has the property of being available for packing with a thousand and one objects -- back to our container and contents.
I think this is far enough -- it is already further than I meant to go.