views:

189

answers:

10

I have an interesting problem. Consider this class hierachy:

class Base
{
public:
   virtual float GetMember( void ) const =0;
   virtual void SetMember( float p ) =0;
};

class ConcreteFoo : public Base
{
public:
   ConcreteFoo( "foo specific stuff here" );

   virtual float GetMember( void ) const;
   virtual void SetMember( float p );

   // the problem
   void foo_specific_method( "arbitrary parameters" );
};

Base* DynamicFactory::NewBase( std::string drawable_name );

// it would be used like this
Base* foo = dynamic_factory.NewBase("foo");

I've left out the DynamicFactory definition and how Builders are registered with it. The Builder objects are associated with a name and will allocate a concrete implementation of Base. The actual implementation is a bit more complex with shared_ptr to handle memory reclaimation, but they are not important to my problem.

ConcreteFoo has class specific method. But since the concrete instances are create in the dynamic factory the concrete classes are not known or accessible, they may only be declared in a source file. How can I expose foo_specific_method to users of Base*?

I'm adding the solutions I've come up with as answers. I've named them so you can easily reference them in your answers.

I'm not just looking for opinions on my original solutions, new ones would be appreciated.

A: 

Add special functions to Base.

The simplest and most unacceptable solution is to add foo_specific_method to Base. Then classes that don't use it can just define it to be empty. This doesn't work because users are allowed to registers their own Builders with the dynamic_factory. The new classes may also have concrete class specific methods.

In the spirit of this solution, is one slightly better. Add generic functions to Base.

class Base
{
   ...
   /// \return true if 'kind' supported
   virtual bool concrete_specific( int kind, "foo specific parameters" );
};

The problem here is there maybe quite a few overloads of concrete_specific for different parameter sets.

caspin
A: 

Just cast it.

When a foo specific method is needed, generally you know that the Base* is actually a ConcreteFoo. So just ensure the definition of class ConcreteFoo is accessible and:

ConcreteFoo* foo2 = dynamic_cast<ConcreteFoo*>(foo);

One of the reasons I don't like this solution is dynamic_casts are slow and require RTTI.

The next step from this is to avoid dynamic_cast.

ConcreteFoo* foo_cast( Base* d )
{
   if( d->id() == the_foo_id )
   {
      return static_cast<ConcreteFoo*>(d);
   }

   throw std::runtime_error("you're screwed");
}

This requires one more method in the Base class which is completely acceptable, but it requires the id's be managed. That gets difficult when users can register their own Builders with the dynamic factory.

I'm not too fond of any of the casting solutions as it requires the user classes to be defined where the specialized methods are used. But maybe I'm just being a scope nazi.

caspin
Would +1 if community wiki (see comments on question). Using an id system is just what RTTI does (though RTTI provides a safer method and implements things differently).
strager
I agree. If you are worried about the performance of dynamic_cast (I wouldn't be), do some tests. Unless you are doing this in a tight loop, I can't imagine that it would be that expensive.
KeithB
+1  A: 

The CrazyMetaType solution.

This solution is not well thought out. I was hoping someone might have had experience with something similar. I saw this applied to the problem of an unknown number of a known type. It was pretty slick. I was thinking to apply it to an unkown number of unknown type*S*

The basic idea is the CrazyMetaType collects the parameters is type safe way, then executing the concrete specific method.

class Base
{
   ...
   virtual CrazyMetaType concrete_specific( int kind ) =0;
};

// used like this
foo->concrete_specific(foo_method_id) << "foo specific" << foo_specific;

My one worry with this solution is that CrazyMetaType is going to be insanely complex to get this to work. I'm up to the task, but I cannot count on future users to be up to be c++ experts just to add one concrete specific method.

caspin
A: 

The cstdarg solution.

Bjarn Stroustrup said:

A well defined program needs at most few functions for which the argument types are not completely specified. Overloaded functions and functions using default arguments can be used to take care of type checking in most cases when one would otherwise consider leaving argument types unspecified. Only when both the number of arguments and the type of arguments vary is the ellipsis necessary

class Base
{
   ...
   /// \return true if 'kind' supported
   virtual bool concrete_specific( int kind, ... ) =0;
};

The disadvantages here are:

  • almost no one knows how to use cstdarg correctly
  • it doesn't feel very c++-y
  • it's not typesafe.
caspin
A: 

Could you create other non-concrete subclasses of Base and then use multiple factory methods in DynamicFactory?

Your goal seems to be to subvert the point of subclassing. I'm really curious to know what you're doing that requires this approach.

Ryan Graham
A: 

If the concrete object has a class-specific method then it implies that you'd only be calling that method specifically when you're dealing with an instance of that class and not when you're dealing with the generic base class. Is this coming about b/c you're running a switch statement which is checking for object type?

I'd approach this from a different angle, using the "unacceptable" first solution but with no parameters, with the concrete objects having member variables that would store its state. Though i guess this would force you have a member associative array as part of the base class to avoid casting to set the state in the first place.

You might also want to try out the Decorator pattern.

notnot
A: 

You could do something akin to the CrazyMetaType or the cstdarg argument but simple and C++-ish. (Maybe this could be SaneMetaType.) Just define a base class for arguments to concrete_specific, and make people derive specific argument types from that. Something like

class ConcreteSpecificArgumentBase;

class Base
{
   ...  
   virtual void concrete_specific( ConcreteSpecificArgumentBase &argument ) =0;
};

Of course, you're going to need RTTI to sort things out inside each version of concrete_specific. But if ConcreteSpecificArgumentBase is well-designed, at least it will make calling concrete_specific fairly straightforward.

Sol
+1  A: 

The cast would be faster than most other solutions, however:

in Base Class add:

void passthru( const string &concreteClassName, const string &functionname, vector<string*> args )
{
    if( concreteClassName == className )
         runPassThru( functionname, args );
}

private:
    string className;
    map<string, int> funcmap;
    virtual void runPassThru( const string &functionname, vector<string*> args ) {}

in each derived class:

void runPassThru( const string &functionname, vector<string*> args )
{
   switch( funcmap.get( functionname ))
   {
      case 1:
          //verify args
          // call function
        break;
      // etc..
   }
}

// call in constructor
void registerFunctions()
{
      funcmap.put( "functionName", id );
      //etc.
}
sfossen
A: 

The weird part is that the users of your DynamicFactory receive a Base type, but needs to do specific stuff when it is a ConcreteFoo.

Maybe a factory should not be used.

Try to look at other dependency injection mechanisms like creating the ConcreteFoo yourself, pass a ConcreteFoo type pointer to those who need it, and a Base type pointer to the others.

total
A: 

The context seems to assume that the user will be working with your ConcreteType and know it is doing so.

In that case, it seems that you could have another method in your factory that returns ConcreteType*, if clients know they're dealing with concrete type and need to work at that level of abstraction.

JohnMcG