Question

I would like to create a simple factory method with a simple C++ syntax:

void *createObject(const char *str,...)
{
  if(!strcmp("X",str))
     return new X(...);
}

I cannot figure out the syntax for this. I've been looking at template metaprogramming and use mpl::vectors, but I am not sure how to pass down this syntax. I want to really avoid using C va_lists if possible and go for a clean syntax like the one above.

Was it helpful?

Solution 4

The solution I ended up using was to create 0, N singletons with templated parameters. It is working pretty well with N = 8. A bit ugly, but only needs to be done once.

OTHER TIPS

This would be a better approach on C++11:

template< typename ...Args >
std::shared_ptr<void> createObject( std::string const& name, Args&& ...args )
{
    if( name == "X" )
    {
        return try_make_shared< X >( std::forward< Args >( args )... );
    }
    /* other cases here*/

    return nullptr;
}

template< typename T, typename ...Args >
typename std::enable_if<
    std::is_constructible< T, Args >::value
  , std::shared_ptr< T >
>::type try_make_shared( Args&&... args )
{
    return std::make_shared< X >( std::forward< Args >( args )... );
}
template< typename T, typename ...Args >
typename std::enable_if<
    !std::is_constructible< T, Args >::value
  , std::shared_ptr< T >
>::type try_make_shared( Args&&... args )
{
    throw std::invalid_argument( "The type is not constructible from the supplied arguments" );
    return nullptr;
}

The differences with your code are

  • It uses a variadic template function instead of an ellipsis argument, thus the number and type of the parameters are still available at compile time (you don't loose type checking). Additionally you can call this function with non-POD types.

  • It returns a shared_ptr<void> instead of a plain void*. This allows you to control from within the factory how the object should be cleaned once all references to it are gone. The user doesn't need to know or care if he should call the standard delete, or maybe a deleteObject method from your factory.

Update: For those suggesting unique_ptr, you can read here about the possibilities that a shared_ptr brings to the table. A restricted factory that does only ever return pointers to new-ly allocated objects may and should use a unique_ptr.

In addition to the code on how to create objects using the nice C++11 variadic templates (as seen in K-ballo's answer), this answer shows how I would handle a set of classes in a project. This method is a big hack and only recommended if you know what you're doing, however, when adding new classes to your project, you only have to add them to a single file listing all your classes, so if the project gets huge, it helps to keep the overview.

Use this approach only, if you have to list your classes multiple times, for example if you also want to have a std::string className() function, for example, returning the name of a class without using C++ runtime type information. Every such function which requires to list all classes in your project can be implemented in a similar way than the following.

classes.h

/* For every class in your project which should be creatable through your
 * factory, add a line here. */
CLASS(Foo)
CLASS(Bar)
CLASS(Baz)

factory.cpp

template< typename ...Args >
std::shared_ptr<void> createObject( std::string const& name, Args&& ...args )
{
    // Define what code to insert for every class:
#define CLASS(T) \
    else if(name == #T) \
        return std::make_shared<T>(std::forward(args)...);

    // List all cases:
    if(0) /*do nothing*/;  // <-- needed because the macro uses else if
#include "classes.h"
#undef CLASS

    return nullptr;
}

If you can't use variadic templates, and don't want to use C-style varargs, your only option is to come up with some common representation for the arguments.

boost::shared_ptr<void> createObject(const char *str,
                                     int argc, const char *argv[])
{
  if(!strcmp("X",str))
     return new X(argc, argv);
  if(!strcmp("Y",str))
     return make_Y(argc, argv);
}

as illustrated for Y, it may be sensible to split the argument handling out into a factory function instead of coupling your constructor to the option format. For example, you might want to switch to a property map or Boost program options.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top