[comp.lang.c++] Virtrual memory allocation

jadam@cs.tamu.edu (James P Adam) (03/20/91)

     In attempting to create some AI-type problem simulators
(e.g., the 3 missionaries && 3 canibals problem), I've found myself
with a memory allocation problem.  First, here is a general outline
of what is going on:
     1) I have created a set of Position classes, all derived from 
an abstract class called Position.  The derived classes can be numerous:
e.g., OneDPos, TwoDPos, etc.  The size of each derived class will
be quite different from the size of other derived classes, and will
certainly be different than the size of the virtual base class itself!
     2) A different class, not derived from Position, will be called
class Move, and this class will store two Position pointers:
   class Move  {
       Position * From;
       Position * To;
   }

   The problem I am having is in the constructors for class Move.  My
basic desire is to allocate space inside these constructors to hold
objects of class Position.  What I originally tried to do was:
    Move::Move( Position& newFrom, Position& newTo )
    {
       From = new( newFrom );  // error checking removed...
       To   = new( newTo   );      // ...for the sake of simplicity
    }
   The compiler doesn't like this, and I can commiserate with it.
Obviously, I can't say something like "From = new( Position )", since
I don't want sizeof( class Position) bytes, I want enough memory
to hold the (unknown) class that's being passed in.
   Three suggestions have been made to me for solving this problem.  
  1) Overload the new operator.  I'm not really sure how this would 
solve my problem, unless I'm supposed to say something like
"From = newFrom.new()", and create a virtual new operator for each class.
  2) Create a sizeOf() operator for each class, which would work as follows:
"From = new( newFrom.sizeOf() )".
  3)  Force the calling process to allocate the space && simply copy a 
pointer in Move's constructor.

    As far as I can tell, each of these options has its strengths and
weaknesses.  In #1 && #2, there is the danger than a derived class might
"forget" to overload the operator, which would cause the inherited 
method to fire, which would undoubtably cause the wrong amount of 
memory to be allocated, which would then cause the program to become
corrupt && work erroneously (but in a way that would be extremely
difficult to track down).
     Option #3 is weak because it requires the "user" programmer to
properly allocate objects prior to each call to a Move constructor.
     Is there an approved-of, or philosophically sound, way of
accomplishing what I am trying to do?
    Thanx:
 Jim
    
   

jgro@lia (Jeremy Grodberg) (03/21/91)

In article <13476@helios.TAMU.EDU> jadam@cs.tamu.edu (James P Adam) writes:
>[How do I create new objects of derived types given only their base class
> references?].
>   The problem I am having is in the constructors for class Move.  My
>basic desire is to allocate space inside these constructors to hold
>objects of class Position.  What I originally tried to do was:
>    Move::Move( Position& newFrom, Position& newTo )
>    {
>       From = new( newFrom );  // error checking removed...
>       To   = new( newTo   );      // ...for the sake of simplicity
>    }
>   The compiler doesn't like this, and I can commiserate with it.
>Obviously, I can't say something like "From = new( Position )", since
>I don't want sizeof( class Position) bytes, I want enough memory
>to hold the (unknown) class that's being passed in.
>   Three suggestions have been made to me for solving this problem.  
>  1) Overload the new operator.  I'm not really sure how this would 
>solve my problem, unless I'm supposed to say something like
>"From = newFrom.new()", and create a virtual new operator for each class.
>  2) Create a sizeOf() operator for each class, which would work as follows:
>"From = new( newFrom.sizeOf() )".
>  3)  Force the calling process to allocate the space && simply copy a 
>pointer in Move's constructor.
>
> [...]
>
>     Option #3 is weak because it requires the "user" programmer to
>properly allocate objects prior to each call to a Move constructor.
>     Is there an approved-of, or philosophically sound, way of
>accomplishing what I am trying to do?

The method which I like best, and which seems to raise the fewest objections,
is to create a virtual method called clone() in your base class (Position),
which returns a copy of the object.  Yes, you must override it everywhere,
but if you forget, you just get the base class part, instead of a core dump
(although to some people this is not an advantage).  Unfortunately, as with
asking an object for its type, there is no way in C++ to have the compiler
do all the work for you; you will have to rely on programming everything
correctly.  Clone(), however, is trivial enough that if you remember to 
do it at all, you will do it right.

-- 
Jeremy Grodberg      "Show me a new widget that's bug-free, and I'll show
jgro@lia.com         you something that's been through several releases."

wmm@world.std.com (William M Miller) (03/21/91)

jgro@lia (Jeremy Grodberg) writes:
> The method which I like best, and which seems to raise the fewest objections,
> is to create a virtual method called clone() in your base class (Position),
> which returns a copy of the object.  Yes, you must override it everywhere,
> but if you forget, you just get the base class part, instead of a core dump
> (although to some people this is not an advantage).  Unfortunately, as with
> asking an object for its type, there is no way in C++ to have the compiler
> do all the work for you; you will have to rely on programming everything
> correctly.  Clone(), however, is trivial enough that if you remember to
> do it at all, you will do it right.

Here's a technique I use for making sure that I don't forget to do things in
a derived class.  It relies on the fact that a virtual base class with no
default constructor MUST be initialized in the most derived class; the
compiler will not allow you to forget to initialize it.  In the example
below, the intializer is intended to be a string containing the name of the
class; the clone() member function can use that value to ensure that it is
not being invoked for an object of an unexpected class, as would be the case
if the author of a derived class neglects to provide a clone() override.

Here's a brief demonstration of the technique:

        #include <stdio.h>
        #include <string.h>
        #include <stdlib.h>

        class my_name_is {
        public:
           my_name_is(const char* nm): my_name(nm) { }

           int this_is(const char* the_name) {
              return strcmp(my_name, the_name) == 0;
              }

           void error() {
              fprintf(stderr, "Need %s::clone()!\a\n", my_name);
              exit(999);
              }

        private:
           const char* my_name;
           };

        class Base: public virtual my_name_is {
        public:
           Base(): my_name_is("Base") { }

           virtual Base* clone() {
              if (!this_is("Base"))
                 error();
              return new Base();
              }

           virtual void say_my_name() {
              printf("Base\n");
              }
           };

        class Derived1: public Base {
        public:
           Derived1(): my_name_is("Derived1") { }

           Base* clone() {
              if (!this_is("Derived1"))
                 error();
              return new Derived1();
              }

           void say_my_name() {
              printf("Derived1\n");
              }
           };

        class Derived2: public Base {
        public:
           Derived2(): my_name_is("Derived2") { }

           // Oops, forgot clone()!

           void say_my_name() {
              printf("Derived2\n");
              }
           };

        int main() {
           Base* bp = new Base;
           bp = bp->clone();
           bp->say_my_name();

           bp = new Derived1;
           bp = bp->clone();
           bp->say_my_name();

           bp = new Derived2;
           bp = bp->clone();
           bp->say_my_name();

           return 0;
           }

If you omit an initialization of "my_name_is" in a class derived from Base,
the compiler will report an error.  The result of running this program is

        Base
        Derived1
        Need Derived2::clone()!

-- William M. Miller, Glockenspiel, Ltd.
   wmm@world.std.com