2

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.

Luc Touraille
  • 79,925
  • 15
  • 92
  • 137
user805547
  • 1,245
  • 1
  • 15
  • 23
  • Since variadic templates are not even supported in Visual Studio 2012, I'm thinking along the lines of instantiating at compile time using template-metaprogramming and then create some sort of functor off of a mpl vector somehow. – user805547 Jan 01 '13 at 20:49
  • 1
    The _November CTP_ adds support for _variadic templates_, get it at http://aka.ms/vc-ctp – K-ballo Jan 01 '13 at 20:56

4 Answers4

8

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.

K-ballo
  • 80,396
  • 20
  • 159
  • 169
  • 1
    `shared_ptr` are left undefined by the standard and their implementation it's platform-dependant, also they can be pretty expensive if they are used in large amount insider your application. I suggest the use of `unique_ptr` unless other kind of smart pointer can be a better choice for what you are aiming at. – user1849534 Jan 01 '13 at 20:17
  • @user1849534: `unique_ptr` is templated on its deleter so it wouldn't fit here, a plain void and a `deleteObject` member function would be better than that. – K-ballo Jan 01 '13 at 20:18
  • 1
    why ? You can't specify a destructor/deleter ? I'm tired and i need to sleep but I can't see your point; a string owned by more than pointer can be pretty nasty. – user1849534 Jan 01 '13 at 20:26
  • For consistency, you should add `std::` to `make_shared`. – leemes Jan 01 '13 at 20:27
  • @leemes: That's not consistency, that was a bug. Thank you. – K-ballo Jan 01 '13 at 20:27
  • Great answer I'm sure this will help many others. Unfortunately, for a variety of reasons, I am still using Visual Studio 10 and gcc 4.2.1 which do not support variadic templates. Is there a solution for older C++ compilers? – user805547 Jan 01 '13 at 20:44
  • 1
    @user805547: There is, but is verbose and cumbersome. Replace `std::shared_ptr` by `boost::shared_ptr`, and see how _perfect forwarding_ is approached in _C++11_ with a number of overloads and the user of `boost::[c]ref` – K-ballo Jan 01 '13 at 20:48
  • `std::forward` needs template arguments, doesn't it? – fredoverflow Jan 01 '13 at 22:13
  • @FredOverflow: Oversight. Thank you. – K-ballo Jan 01 '13 at 22:14
  • It really should return unique_ptr. shared_ptr is so special-purpose it's not worth mentioning most of the time (and so are custom deleters) – Cat Plus Plus Jan 01 '13 at 22:15
  • @Cat Plus Plus: That was mentioned already, custom deleters are why `shared_ptr` was chosen. You may be returning a pointer to a shared member or to a static object that better not be deleted. If you can restrict the usability of your factory so that it will only ever return pointers to newly allocated objects that are deleted using `delete` then a `unique_ptr` would be more appropriate, but this is a general answer – K-ballo Jan 01 '13 at 22:25
  • Is boost::any not an option here? – StackedCrooked Jan 03 '13 at 03:33
  • No, `shared_ptr` is not a general solution (and especially not `shared_ptr`). It's a specialised tool with very specific use cases. This is not one of them. This doesn't need shared ownership. This function probably shouldn't even exist. – Cat Plus Plus Jan 03 '13 at 03:33
  • @CatPlusPlus: Have you read the implementation advantages that `shared_ptr` has? If you don't need nor want any of them then you should use `unique_ptr`, as the answer already mentions – K-ballo Jan 03 '13 at 03:37
0

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;
}
leemes
  • 44,967
  • 21
  • 135
  • 183
0

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.

Useless
  • 64,155
  • 6
  • 88
  • 132
0

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.

user805547
  • 1,245
  • 1
  • 15
  • 23