richard@pantor.UUCP (Richard Sargent) (02/13/90)
[Note: long message (~160 lines) follows.]
Earlier, I wrote asking how to handle creating a class hierarchy
of display devices, for example, and to automatically create the
correct derived instance depending on which display was in use.
I received a number of useful answers, for which I thank their
authors. They all had a common theme that you need a switch
statement somewhere, usually as a standalone function, such as
create_display().
The colleague for whom I asked the question took those answers
and came up with the following solution. Basically, he did not
like having the switch statement outside the class hierarchy.
He developed this code to allow the base class to create a derived
object. We have identified that there *may* a number of cases where
this capability is desirable. As with any off-beat solution, it
needs be used with appropriate cautions and restraint.
I will entertain no flames on this particular issue.
We reason that the switch logic has to be somewhere, so it might
as well be with the base class, so it can be easily found and
modified as new derived classes are defined. Obviously, the
best situations for using this solution are when the class
hierarchy is subject to very few additions, such as a hierarchy
of display devices.
This solution was implemented using version 1.2 of the language.
It appears to be well defined with regard to any language definitions
we have seen. It appears to be implementation independent.
The question for the language lawyers (and author, too!) is
whether this will remain a valid solution as the language evolves?
Other questions include "is there a better way to have a base class
create a derived class object?" and "if C++ 3.0 eliminates assignment
to 'this', will the language be modified to provide a simple means
to have the base class create an instance of a derived class?"
-------------------------------------------------------------------------
Note: this solution is free for anyone to use in anyway they feel
fit. It is in the public domain. We only ask for "credit due where
credit deserved". In this case, that is "Martin Miller, Pansophic
Systems, Inc.".
-------------------------------------------------------------------------
Example follows:
Developed using Intek C++ 1.2 and Microsoft C 5.1 under MS-DOS.
For those who try this on other systems, please let me know whether
this implementation works as is and/or what revisions you have to
make to get it to work. If you have problems getting the desired
results, I have a longer version that reports a lot of debugging
information which I can mail you if you need it.
I'm looking forward to all your replies!
// sample showing how a BASE class constructor be used to create
// DERIVED class objects
#include <stream.h>
short maketype; // simple way to determine what DERIVED class
// the BASE() class constructor creates
class BASE {
protected:
// must contain assignment to this (that is always executed)
BASE( BASE *bp ) { if( this == 0 ) this = bp; } // only if an actual BASE class object was wanted
public:
BASE();
virtual ~BASE() {} // if wanted
virtual void iam() { cout << "I am a BASE()\n"; }
};
class DERIVED1 : public BASE {
protected:
short junk1;
// must contain assignment to this (that is always executed)
DERIVED1( BASE *bp ) : (bp) { this = (DERIVED1 *)bp; junk1 = 0; }
public:
virtual ~DERIVED1() {} // if wanted
virtual void iam() { cout << "I am a DERIVED1()\n"; }
};
class DERIVED2 : public BASE {
protected:
short junk1;
short junk2;
// must contain assignment to this (that is always executed)
DERIVED2( BASE *bp ) : (bp) { this = (DERIVED2 *)bp; junk1 = junk2 = 0; }
public:
virtual ~DERIVED2() {} // if wanted
virtual void iam() { cout << "I am a DERIVED2()\n"; }
};
// must contain assignment to this (that is always executed)
BASE::BASE() {
if( this ) { // make sure it's NOT a static or auto variable
cout << "error: BASE class objects must be dynamically allocated\n";
exit(1);
}
switch( maketype ) {
case 0:
this = (BASE *)new char[sizeof(BASE)];
new BASE(this);
break;
case 1:
this = (BASE *)new char[sizeof(DERIVED1)];
new DERIVED1(this);
break;
case 2:
this = (BASE *)new char[sizeof(DERIVED2)];
new DERIVED2(this);
break;
}
}
main() {
maketype = 0;
BASE *bp0 = new BASE;
bp0->iam();
delete bp0;
cout << "\n";
maketype = 1;
BASE *bp1 = new BASE;
bp1->iam();
delete bp1;
cout << "\n";
maketype = 2;
BASE *bp2 = new BASE;
bp2->iam();
delete bp2;
}
--------------------------------------------------------------------------
Note: the use of the global "maketype" is only one of many possible
mechanisms to identify which derived class should be made. In the
example of a display device hierarchy, the base class would include
an "identify_display()" funtion.
--------------------------------------------------------------------------
This program should produce the following results:
I am a BASE()
I am a DERIVED1()
I am a DERIVED2()
--------------------------------------------------------------------------
Richard Sargent Internet: richard@pantor.UUCP
Systems Analyst UUCP: ...!mnetor!becker!pantor!richardshopiro@alice.UUCP (Jonathan Shopiro) (02/15/90)
In article <52.UUL1.3#5109@pantor.UUCP>, richard@pantor.UUCP (Richard Sargent) writes: > [Long discussion omitted -- essentially the problem is to create an object without knowing its exact type. Sargent gives a solution (credited to Martin Miller) that has the user code create a base class object and then the base class constructor uses an assignment to this to arrange that the result is the appropriate derived class object.] Please don't use this technique. Assignment to ``this'' results in a warning in C++ 2.0 and will probably be disallowed completely in future versions. It has always been an ugly wart on the language and getting rid of it will allow a much better implementation of constructors. Following is a revision of Sargent's example code with assignment to ``this'' eliminated. The only change to the interface is that the user now says BASE::new_BASE() where he used to say new BASE Also some error checking has been moved from run-time to compile-time and the code is a little cleaner and more efficient. Otherwise I have tried to minimize the changes to Sargent/Miller's code. // sample showing how a BASE class static member function can be // used to create DERIVED class objects #include <stream.h> short maketype; // simple way to determine what DERIVED class // the BASE() class constructor creates class BASE { public: BASE(); static BASE* new_BASE(); virtual ~BASE() {} // if wanted virtual void iam() { cout << "I am a BASE()\n"; } }; class DERIVED1 : public BASE { public: virtual ~DERIVED1() {} // if wanted virtual void iam() { cout << "I am a DERIVED1()\n"; } }; class DERIVED2 : public BASE { public: virtual ~DERIVED2() {} // if wanted virtual void iam() { cout << "I am a DERIVED2()\n"; } }; BASE* BASE::new_BASE() { switch( maketype ) { case 0: return new BASE; case 1: return new DERIVED1; case 2: return new DERIVED2; } } main() { maketype = 0; BASE *bp0 = BASE::new_BASE; bp0->iam(); delete bp0; cout << "\n"; maketype = 1; BASE *bp1 = BASE::new_BASE; bp1->iam(); delete bp1; cout << "\n"; maketype = 2; BASE *bp2 = BASE::new_BASE; bp2->iam(); delete bp2; } If the function BASE::new_BASE is to be used to create all objects in the BASE hierarchy, then you should make all the constructors protected and make all the derived classes friends of the base class. If you have an old version of C++ without static member functions, make new_BASE an external function and make it a friend of all the classes in the hierarchy. -- Jonathan Shopiro AT&T Bell Laboratories, Warren, NJ 07060-0908 research!shopiro (201) 580-4229