dougo@soda.berkeley.edu (Doug Orleans) (05/28/91)
Recently I began my first major C++ programming project. I am using Turbo C++ 1.0 on an XT clone. Turbo C++ comes with a fairly extensive class heirarchy, so I decided to use that as a base. I ran up against a major problem, however, when I tried to use multiple inheritance, and I came up with two solutions: one is a kludge, and the other involves a change in the language definition. My problem was this: there is a class Object which lies at the root of the heirarchy. One branch of the tree is the Container branch, which has classes such as Container, Collection, and Array. Another branch is the Sortable branch, which has classes of objects which can be sorted, such as String. I wanted to create a class which was a Sortable Array, so I inherited it from both classes. As the heirarchy is now, Container and Sortable have Object as non-virtual base classes. Thus my new class, SortableArray, has two Objects inside it. This is a problem, because the generic operators such as << and == are defined to operate on Objects, but a SortableArray cannot be implicitly cast to an Object. Rather than explicitly cast a SortableArray to an Array or a Sortable every time I want to use it, I decided to make Object a virtual base class of Container and Sortable. This caused another problem, because you cannot cast downwards from a virtual base class to a derived class. The main place this caused a problem was in the virtual function isEqual, which is a pure virtual function in Object. (This is what the operator == calls.) The function isEqual takes an Object reference as its argument. Thus my SortableArray class defines isEqual(Object &O), in which O is cast to a SortableArray so that it can be compared to *this. But an Object reference cannot be cast down to a SortableArray reference. My kludgey solution to this was to define a pure virtual function in Object called me. In every non-abstract class defined from Object, me returns a void pointer to this, i.e. (void *)this. Since any pointer can be cast to a void pointer, this is legal. Then in SortableArray's isEqual function, to cast Object &O to a SortableArray reference, I call O's me function, and cast that: SortableArray &S = *(SortableArray *)O.me() Since a void pointer may be cast to any other pointer, this is legal. This seems to work, although I have some other bugs which may or may not have anything to do with this. My idea to change the language to remedy this problem is to allow overloaded virtual functions. This is best described with an example: class Base { virtual int f(Base &); } class Derived : public Base { virtual int f(Derived &); } Base B; Derived D; In this example, D.f(B) will call B::f, D.f(D) will call D::f, and B.f(B) will call B::f, as it is now; but B.f(D) will call D::f, rather than B::f. Will this work? Is this feasible? Is there some other situation where you would prefer to have it the way it is now? Can you suggest a better way to solve my problem? Please send me email, and I will post a summary of the advice given. Doug Orleans dougo@soda.berkeley.edu
howells@frieda.mitre.org (Timothy P. Howells) (05/28/91)
Hi, dougo@soda.berkeley.edu (Doug Orleans) wrote about the fact that C++ doesn't permit you to cast down from a virtual base class to one of its derived classes. I had the same problem with the prohibition against casting down from a virtual base class. I also tried beating the compiler by first casting to (void *), but I found that this caused errors in accessing member values. I suspect your bugs are indeed related to the kludge. My "solution" was to reference all my objects via unions with tags indicating the types. Of course this works, but it seems to defeat the purpose of object oriented programming to a large extent. Another possible "solution" is to use a virtual fuction to retrieve the data value, but it seems silly to use a function call (which must be resolved at run time) when all you really want is a data reference. This approach also forces you to declare a stub virtual function in the parent. I'm new to C++. Am I missing something here, or is this a flaw in the language? Since the limitation only applies to *virtual* base classes, I assume that there is some implementation problem regarding multiple inheritance. On the other hand, if unions can be made to work, why not casting downwards?
Doug.Orleans@sunbrk.FidoNet.Org (Doug Orleans) (05/28/91)
Recently I began my first major C++ programming project. I am using Turbo C++ 1.0 on an XT clone. Turbo C++ comes with a fairly extensive class heirarchy, so I decided to use that as a base. I ran up against a major problem, however, when I tried to use multiple inheritance, and I came up with two solutions: one is a kludge, and the other involves a change in the language definition. My problem was this: there is a class Object which lies at the root of the heirarchy. One branch of the tree is the Container branch, which has classes such as Container, Collection, and Array. Another branch is the Sortable branch, which has classes of objects which can be sorted, such as String. I wanted to create a class which was a Sortable Array, so I inherited it from both classes. As the heirarchy is now, Container and Sortable have Object as non-virtual base classes. Thus my new class, SortableArray, has two Objects inside it. This is a problem, because the generic operators such as << and == are defined to operate on Objects, but a SortableArray cannot be implicitly cast to an Object. Rather than explicitly cast a SortableArray to an Array or a Sortable every time I want to use it, I decided to make Object a virtual base class of Container and Sortable. This caused another problem, because you cannot cast downwards from a virtual base class to a derived class. The main place this caused a problem was in the virtual function isEqual, which is a pure virtual function in Object. (This is what the operator == calls.) The function isEqual takes an Object reference as its argument. Thus my SortableArray class defines isEqual(Object &O), in which O is cast to a SortableArray so that it can be compared to *this. But an Object reference cannot be cast down to a SortableArray reference. My kludgey solution to this was to define a pure virtual function in Object called me. In every non-abstract class defined from Object, me returns a void pointer to this, i.e. (void *)this. Since any pointer can be cast to a void pointer, this is legal. Then in SortableArray's isEqual function, to cast Object &O to a SortableArray reference, I call O's me function, and cast that: SortableArray &S = *(SortableArray *)O.me() Since a void pointer may be cast to any other pointer, this is legal. This seems to work, although I have some other bugs which may or may not have anything to do with this. My idea to change the language to remedy this problem is to allow overloaded virtual functions. This is best described with an example: class Base { virtual int f(Base &); } class Derived : public Base { virtual int f(Derived &); } Base B; Derived D; In this example, D.f(B) will call B::f, D.f(D) will call D::f, and B.f(B) will call B::f, as it is now; but B.f(D) will call D::f, rather than B::f. Will this work? Is this feasible? Is there some other situation where you would prefer to have it the way it is now? Can you suggest a better way to solve my problem? Please send me email, and I will post a summary of the advice given. Doug Orleans dougo@soda.berkeley.edu * Origin: Seaeast - Fidonet<->Usenet Gateway - sunbrk (1:343/15.0)
Timothy.P..Howells@sunbrk.FidoNet.Org (Timothy P. Howells) (05/28/91)
Hi, dougo@soda.berkeley.edu (Doug Orleans) wrote about the fact that C++ doesn't permit you to cast down from a virtual base class to one of its derived classes. I had the same problem with the prohibition against casting down from a virtual base class. I also tried beating the compiler by first casting to (void *), but I found that this caused errors in accessing member values. I suspect your bugs are indeed related to the kludge. My "solution" was to reference all my objects via unions with tags indicating the types. Of course this works, but it seems to defeat the purpose of object oriented programming to a large extent. Another possible "solution" is to use a virtual fuction to retrieve the data value, but it seems silly to use a function call (which must be resolved at run time) when all you really want is a data reference. This approach also forces you to declare a stub virtual function in the parent. I'm new to C++. Am I missing something here, or is this a flaw in the language? Since the limitation only applies to *virtual* base classes, I assume that there is some implementation problem regarding multiple inheritance. On the other hand, if unions can be made to work, why not casting downwards? * Origin: Seaeast - Fidonet<->Usenet Gateway - sunbrk (1:343/15.0)
chip@tct.com (Chip Salzenberg) (06/01/91)
According to Doug.Orleans@sunbrk.FidoNet.Org (Doug Orleans): >My problem was this: there is a class Object which lies at the root >of the heirarchy. One branch of the tree is the Container branch, >which has classes such as Container, Collection, and Array. This approach, with all (interesting) classes derived from Object, is sometimes called a "type tree". In my opinion, a type tree is a poor match for the C++ language. I avoid it. I take care in my C++ programming not to lose the static type of any object for which I will need that static type later on. Obviously, this means that I cannot use just one Array class, one Bag class, etc. It's better to be type-safe than to be convenient. -- Chip Salzenberg at Teltronics/TCT <chip@tct.com>, <uunet!pdn!tct!chip> perl -e 'sub do { print "extinct!\n"; } do do()'
pkr@media03.UUCP (Peter Kriens) (06/03/91)
Chip Salzberg writes: > I take care in my C++ programming not to lose the static type of any > object for which I will need that static type later on. Obviously, > this means that I cannot use just one Array class, one Bag class, etc. > > It's better to be type-safe than to be convenient. Is there any research done validates this statement? As far as I can see there are pro's and con's for both methods. In the C++ language being typesafe means code duplication and handling a lot more cases. Resulting in a program with lots more lines of code and minimising reuse. Type safe means that you got your types ok, but not all the other 90% or mistakes you can make. So evenif it compiles, it probably doesnt run at once. So which one wins,lesser code ( is I assume lesser bugs) and more reuse, or the type safety? Peter Kriens
mnm@hpcupt3.cup.hp.com (Michey Mehta) (06/04/91)
If you have NIH sources, you might want to see how they handle castdowns from a virtual base. There was also an article by Desmond D'Souza on this topic in the July/August issue of The C++ Report. Michey Mehta mnm@hpda.hp.com
chip@tct.com (Chip Salzenberg) (06/04/91)
According to pkr@media03.UUCP (Peter Kriens): >Chip Salzberg writes: >> I take care in my C++ programming not to lose the static type of any >> object for which I will need that static type later on. Obviously, >> this means that I cannot use just one Array class, one Bag class, etc. >> >> It's better to be type-safe than to be convenient. > >Is there any research done validates this statement? Not as far as I know. My personal experience and my understanding of the strengths and weaknesses of C++ led me to that conclusion. >In the C++ language being typesafe means code duplication and handling >a lot more cases. Not really. The preprocessor is a wonderful thing, and templates are even better. I cannot create just one Array class, for example; but with the preprocessor, I can automatically generate StringArray, IntArray, etc. without any additional coding. And templates bring the effort required to create specialized classes down to almost zero. >So which one wins,lesser code ( is I assume lesser bugs) and >more reuse, or the type safety? Reuse is not much of an issue, thanks to the preprocessor and templates. Therefore, for me, the only remaining issue is bugs. And I'd prefer to catch typing bugs at compile time instead of letting them lie in wait to bite me at run time. "Never put off until run time that which can be done at compile time." -- Chip Salzenberg at Teltronics/TCT <chip@tct.com>, <uunet!pdn!tct!chip> perl -e 'sub do { print "extinct!\n"; } do do()'
gary@matterhorn.ctc.contel.com (Gary Bisaga x4219) (06/05/91)
In article <45680006@hpcupt3.cup.hp.com>, mnm@hpcupt3.cup.hp.com (Michey Mehta) writes: > If you have NIH sources, you might want to see how they handle castdowns > from a virtual base. There was also an article by Desmond D'Souza on > this topic in the July/August issue of The C++ Report. I realize this is probably a FAQ, but where does one get ahold of the NIH sources? Also, where does one get ahold of a FAQ? In the time I have been reading this list I have seen several references to the FAQ but nobody saying where to get it from. Gary Bisaga (gary@ctc.contel.com)