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!richard
shopiro@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