Dynamically choosing what to "new"

P

Pat

Hi,

I've run into a strange problem, but one that seems like it might be
fairly common.

I have a single base class, from which several other classes are derived.
To keep the example simple, the base class is "Animal" and the derived
classes are "Cat," "Dog," and "Horse."

The base class has a pure virtual method:

virtual void GetName(std::string *s) = 0;

All the derived classes are required to implement this method and return
the appropriate string, for example:

void Dog::GetName(std::string *s)
{
*s = "Dog";
}

So when the user clicks a "Save" button, a text file is written by
calling the GetName() method on all the existing Animal objects. So the
file on disk would look something like this:

Dog, Cat, Dog, Dog, Horse, Cat

Later, when the user clicks a "Load" button, I'd like to recreate all
those animals. What's a clean way to do this? My first thought was just
to test each string with a big if-else block, like so:


while (ReadNextToken(input_string))
{
Animal *a;

if (input_string == "Dog")
{
a = new Dog;
}
else if (input_string == "Cat")
{
a = new Cat;
}
else if (input_string == "Horse")
{
a = new Horse;
}
else
{
ErrorMessage()
}
}

But that's ugly and not object-oriented at all. It's also error-prone as
I add more and more classes, because I have to remember to manually add a
new "else if" block for every new class and I have to manually ensure
that the strings match the GetName() result.

Any ideas for a better approach would be appreciated.

Thanks,
Pat
 
S

Steve Folly

Any ideas for a better approach would be appreciated.

You might want to investigate the Factory design pattern.

--
Regards,
Steve

"...which means he created the heaven and the earth... in the DARK! How good
is that?"
 
G

Gianni Mariani

Pat said:
Hi,

I've run into a strange problem, but one that seems like it might be
fairly common.

It is common.

I don't know of any other generic factory implementations but the one in
Austria C++ will allow you to register a number of different factories
(named Dog, Cat etc) and simply request construction on an appropriate one.


#include "at_factory.h"

class Animal
{

};

int main( int argc, char ** argv )
{

if ( argc == 2 )
{

Animal * animal = at::FactoryRegister<
Animal, at::DKy
>::Get().Create( argv[1] )();

// the Create method takes a few other arguments
// one that tells it to throw on failure
if ( ! animal )
{
return 1;
}

}
};

// this stuff below can be in any source file or even DLL/DSO

struct Dog : Animal
{
};

// AT_MakeFactory0P is a macro that "registers" a factory
// for Dog under the key "Dog" over it's interface Animal
// using a key type of at::DKy - you could use std::string
// here if you wanted to.
AT_MakeFactory0P( "Dog", Dog, Animal, at::DKy );


struct Cat : Animal
{
};

AT_MakeFactory0P( "Dog", Cat, Animal, at::DKy );


Careful that you make sure the factory object files are linked in.
That's the usual source of frustration using this. There are a couple
of tools which help you do that but it's the only issue.
 
J

James Kanze

I've run into a strange problem, but one that seems like it might be
fairly common.
I have a single base class, from which several other classes are derived.
To keep the example simple, the base class is "Animal" and the derived
classes are "Cat," "Dog," and "Horse."
The base class has a pure virtual method:
virtual void GetName(std::string *s) = 0;
All the derived classes are required to implement this method and return
the appropriate string, for example:
void Dog::GetName(std::string *s)
{
*s = "Dog";
}

Not related to your problem, but is there any reason for passing
a poniter to a string, and not simply returning a string.
So when the user clicks a "Save" button, a text file is written by
calling the GetName() method on all the existing Animal objects. So the
file on disk would look something like this:
Dog, Cat, Dog, Dog, Horse, Cat
Later, when the user clicks a "Load" button, I'd like to recreate all
those animals. What's a clean way to do this?

The usual situation is some sort of registry with either
pointers to functions, or pointers to an abstract Factory
object. Using the abstract factory object method, this might
look like:

class Factory
{
public:
virtual ~Factory() {}
virtual Animal* create() const = 0 ;

protected:
explicit Factory( std::string const& name ) ;
} ;
typedef std::map< std::string, Factory const* >
FactoryMap ;
FactoryMap factoryMap ;

Factory::Factory(
std::string const& name )
{
FactoryMap::iterator
elem = factoryMap.find( name ) ;
assert( elem == factoryMap.end() ) ;
factoryMap.insert( FactoryMap::value_type( name, this ) ) ;
}

template< typename T >
class ConcreteFactory : public Factory
{
public:
explicit ConcreteFactory( std::string const& name )
: Factory( name )
{
}

virtual Animal* create() const
{
return new T ;
}
} ;

Given that, you just declare a static instance of a factory for
each type you're interested in. (Watch out for order of
initialization issues, though. In practice, I usually make the
map a singleton.)
 
P

Pat

Not related to your problem, but is there any reason for passing
a poniter to a string, and not simply returning a string.


Yes, passing a pointer is more efficient because it avoids copying the
entire object onto the stack. I try to always pass pointers or references,
unless the parameter is small (e.g. int). In this one simplified case it
might not make much difference, since the "=" operator is going to
basically perform a copy anyway. But for real world functions, it's a good
idea.


[code snipped]
Given that, you just declare a static instance of a factory for
each type you're interested in. (Watch out for order of
initialization issues, though. In practice, I usually make the
map a singleton.)


Thank you, this is exactly what I needed. And thanks to the others who
also suggested the "factory" design pattern. I am educated! I must have
spaced out on the day that was covered in my abstract data structures
class. Thanks again.

Pat
 
C

Clark Cox

Yes, passing a pointer is more efficient because it avoids copying the
entire object onto the stack.

Are you sure about that? Many compilers will do return-value
optimization. Additionally, copying an instance of std::string may be a
lot less expensive than you think it is (for instance, many
implementations are copy-on-write).

The morals of the story: don't optimize without profiling, and all
generalizations are wrong :)
 
J

James Kanze

Yes, passing a pointer is more efficient because it avoids copying the
entire object onto the stack.

More efficient in what? It means that the user has to declare
extra objects, and manage them, which is a definite loss of
programmer efficiency. So until you've measured a problem, it's
pure stupitidy to not use a return value. Especially because it
is just as often more efficient to use the return value; there
is no hard and fast rule.
I try to always pass pointers or references,
unless the parameter is small (e.g. int).

Which is just stupid.
In this one simplified case it
might not make much difference, since the "=" operator is going to
basically perform a copy anyway. But for real world functions, it's a good
idea.

Nonsense.
 
J

JohnQ

All the derived classes are required to implement this method and return
the appropriate string, for example:
void Dog::GetName(std::string *s)
{
*s = "Dog";
}

"Not related to your problem, but is there any reason for passing
a poniter to a string, and not simply returning a string."

Or better still, a reference so null-checking can be eliminated.

But, my preference is that "get functions" return what they are getting, and
not prepending "get" to the function name, and Name() seems better as a base
class non-virtual function:

const char* Animal::Name()
{
return name; // ptr to a char array containing "Dog"
}

or (for those who prefer "fat C++" over "veneer C++" style coding):


// note that reference return allows this function to be used as a setter
also
// if it wasn't const but in this case of usage setting is not appropriate
for the
// data member which will be initialized at construction somehow.
//
const std::string& Animal::Name()
{
return name; // ptr to a string containing "Dog"
}

I don't see any reason to hide-away "Dog" as a string literal in one of the
methods (does anyone else?). It's data, so why make it "code-like"?

(LOL, I was just going to post about preferably passing in references rather
than pointers, and then I started thinking a bit about the actual context
and design).

John
 
J

JohnQ

Clark Cox said:
Are you sure about that? Many compilers will do return-value optimization.
Additionally, copying an instance of std::string may be a lot less
expensive than you think it is (for instance, many implementations are
copy-on-write).

The morals of the story: don't optimize without profiling, and all
generalizations are wrong :)

Note that "copy-on-write" _is_ an optimization.

John
 
J

JohnQ

I try to always pass pointers or references,
unless the parameter is small (e.g. int).

"Which is just stupid."

My rule of thumb is "pass a reference when you can, pass a pointer when you
have to" as arguments to functions. Copy-on-write may be a technique for
severely constrained platforms rather than a general technique to be used
everywhere (an exercise in futility?).

John
 
G

Gianni Mariani

JohnQ wrote:
....
Note that "copy-on-write" _is_ an optimization.

Yah - but it's an optimization made by the compiler not be me.

Also, as a side note, it appears that the std::string interface is too
loose to be able to provide an optimal and flawless copy on write
optimization. GCC is at least planning to abandon its copy on write
std::sting impl.
 
G

Gianni Mariani

JohnQ said:
"Which is just stupid."

My rule of thumb is "pass a reference when you can, pass a pointer when you
have to" as arguments to functions. Copy-on-write may be a technique for
severely constrained platforms rather than a general technique to be used
everywhere (an exercise in futility?).

What exact point are you trying to make with COW ? It seems like it has
very common usage..
 
J

JohnQ

Gianni Mariani said:
JohnQ wrote:
...

Yah - but it's an optimization made by the compiler not be me.

No, no, it's coded into the string class (user-level stuff).
Also, as a side note, it appears that the std::string interface is too
loose to be able to provide an optimal and flawless copy on write
optimization. GCC is at least planning to abandon its copy on write
std::sting impl.

I did so in my own string class a few years ago also after reading a
discussion about the non-necessity of it and the potential problems in a
multi-threaded environment.

John
 
J

JohnQ

Gianni Mariani said:
What exact point are you trying to make with COW ? It seems like it has
very common usage..

"don't use it if you don't have to". I don't think it has a place at lower
level code for general usage because it complicates things unnecessarily.
COW looks like one of those "oh, look what neat things one can do with C++"
things that is general knowledge but should be used judiciously. That is to
say that it should probably be avoided (pass a reference instead and "go
ahead and make a copy" when you need to because chances are that CPU power
and memory is not an issue, but code maintenance and reliability is). (Ref:
Keep It Simple Stupid, but not "simply stupid" or "stupidly simple" either).

I couldn't find the link to the site that discussed the COW issues
(especially in an MT environment) in depth, but google is your friend, and
maybe you can find something similar.

John
 
G

Gianni Mariani

JohnQ said:
No, no, it's coded into the string class (user-level stuff).

I lumped the STL as part of the "compiler" in the statement above. The
issue here is WHO does the optimization not that it is done or not.
 
J

James Kanze

What exact point are you trying to make with COW ? It seems like it has
very common usage..

It's extremely difficult to get both right and with acceptable
performance in a multithreaded environment. For
std::basic_string, I only know of two COW implementations: Sun
CC is unacceptably slow, and g++ has bugs. Admittedly, a large
part of the problem here is that std::basic_string has a
particularly pernicious interface. But I find that while I made
extensive use of COW when I started C++, some 15 years ago, I
rarely if ever use it today. Improvements in machine speed, and
above all allocation algorithms, coupled with the widespread use
of multithreading, have made it largely obslete today.
 
P

Pat

Interesting thread... The efficiency of the "GetName()" method wasn't
really a concern, because it was just a function I made up to illustrate my
problem.

But in response to the comments about passing by addres vs passing by
reference vs passing by value, I really don't understand the arguments
against passing pointers as a parameter. It's guaranteed to be fast,
without requiring tricky compiler optimizations. And if you do it as a
rule, you don't really have a problem with it "adding complexity."

But complexity issues aside (I don't really see what they are, anyway), as
I initially said, in "real world" functions, I don't see a way around
passing pointers. If you've got a function that takes several large
objects as parameters, accesses various members of each, then returns some
value as a result, I think you'd be crazy to pass in the objects as copies.

I've really never given it much thought. It has just been an accepted rule
of thumb at places where I've worked -- mainly game development, where
performance is crucial.

I'm willing to believe that a smart compiler might be able to optimize
pass-by-value to be *as good* as pass-by-reference, in some cases, but I
don't really see what you gain, besides maybe a longer compile time.
 
I

Ian Collins

Pat said:
Interesting thread... The efficiency of the "GetName()" method wasn't
really a concern, because it was just a function I made up to illustrate my
problem.

But in response to the comments about passing by addres vs passing by
reference vs passing by value, I really don't understand the arguments
against passing pointers as a parameter. It's guaranteed to be fast,
without requiring tricky compiler optimizations. And if you do it as a
rule, you don't really have a problem with it "adding complexity."
It may be less efficient and more complex to pass a string by pointer.
Consider the case where you want to initialise a string from an object
method, so your getName():

std::string name;
object.getName( &name );

now if getName() where declared as

std::string& getName() const { return someName; }

one could simply write

std::string name( object.getName );

I can't think of any situation where passing a string by pointer would
be the preferred solution. Passing by (const) reference should be used
instead.
But complexity issues aside (I don't really see what they are, anyway), as
I initially said, in "real world" functions, I don't see a way around
passing pointers. If you've got a function that takes several large
objects as parameters, accesses various members of each, then returns some
value as a result, I think you'd be crazy to pass in the objects as copies.
Pass by (const) reference.
I've really never given it much thought. It has just been an accepted rule
of thumb at places where I've worked -- mainly game development, where
performance is crucial.

I'm willing to believe that a smart compiler might be able to optimize
pass-by-value to be *as good* as pass-by-reference, in some cases, but I
don't really see what you gain, besides maybe a longer compile time.

The key to returning by value is the common optimisation Return Value
Optimisation. RVO makes the example getName() above very efficient.
 
I

Ian Collins

Ian said:
It may be less efficient and more complex to pass a string by pointer.
Consider the case where you want to initialise a string from an object
method, so your getName():

std::string name;
object.getName( &name );

now if getName() where declared as

std::string& getName() const { return someName; }

one could simply write

std::string name( object.getName );
std::string name( object.getName() );
 
J

JohnQ

Gianni Mariani said:
I lumped the STL as part of the "compiler" in the statement above.

I know, that's why I posted: it's incorrect.
The issue here is WHO does the optimization not that it is done or not.

Oh, I thought it was whether COW is worthwhile or not.

John
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,743
Messages
2,569,478
Members
44,899
Latest member
RodneyMcAu

Latest Threads

Top