[comp.lang.c++] Question for language lawyers

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