1

Inspired by answer Johannes Schaub's answer I tried to implement a reflective factory. The idea is that you can create objects by passing the name of the class to a method which creates the concerning object on the heap and returns a pointer to a common base class. Using dynamic_cast and RTTI the objects can be casted back to their original type if necessary. One should be able to use the reflective factory as shown below:

// Base is the base class of all objects to create.
class Factory: public AbstractFactory<Base>
{
public:
    Factory()
    {
        m_map["DerivedA"] = &AbstractFactory::createInstance<DerivedA>;
        m_map["DerivedB"] = &AbstractFactory::createInstance<DerivedB>; 
    }
};

int main()
{
    Factory factory;
Base *a = factory.Create("DerivedA");
DerivedA *aa = dynamic_cast<DerivedA*>(a);

    // etc..
}

I got this working so far. However there are two main problems I have with the code below. It is ugly and if I make the methods of the abstract factory protected it complaints that it cannot access the createInstance() method. This is something I do not understand. By definition the derived class should be able to access the protected methods of the base class. I tested the code in VS 2008. Further notes: I know the base class is not really abstract, because it does not contain a pure virtual function. I know about std::function however so far I haven't used it. Maybe I'll do in the future.

#include <iostream>
#include <map>
#include <typeinfo>
#include <string>

struct Base
{
    virtual std::string SayHello() = 0;
};

struct DerivedA: public Base
{

    virtual std::string SayHello() 
    {
        return "Hello from DerivedA";
    }

};

struct DerivedB: public Base
{

    virtual std::string SayHello() 
    {
        return "Hello form DerivedB";
    }

};

/**
 * @brief Reflective Factory class. Creates objects of classes which derive from
 *        a common base class.
 *
 */
template<class BASE_T>
struct AbstractFactory
{

// Macro to call ptrs to member functions as recommended in the C++ FAQ lite.
// http://www.parashift.com/c++-faq-lite/pointers-to-members.html
#define CALL_MEMBER_FN(object, ptrToMember) ((object).*(ptrToMember))

    // Recall funcion ptr syntax for members: ReturnType (class::*) (Arguments)

    // using a typedef makes it easier..
    typedef BASE_T*  (AbstractFactory::*func_ptr_type) ();
    // retType^ ClassName^ AliasName^  Arguments^

    typedef std::map<std::string, func_ptr_type> map_type;

    template<typename DERIVED_T> 
    BASE_T * createInstance() 
    { return new DERIVED_T; }

    map_type m_map;

    /**
     * @brief Creates an object from a class with the name given as string.             
     */
    BASE_T * Create(std::string className)
    {
        // Note the last () at the end.
        return CALL_MEMBER_FN(*this, m_map[className])();
    }

#undef CALL_MEMBER_FN

};

class Factory: public AbstractFactory<Base>
{

public:

    Factory()
    {
        m_map["DerivedA"] = &AbstractFactory::createInstance<DerivedA>;
        m_map["DerivedB"] = &AbstractFactory::createInstance<DerivedB>; 
    }

};

int main()
{
    Factory factory;

    Base *a = factory.Create("DerivedA");

    DerivedA *aa = dynamic_cast<DerivedA*>(a);

    std::cout << typeid(a).name()  << std::endl;
    std::cout << typeid(*a).name()  << std::endl;
    std::cout << typeid(aa).name() << std::endl;

    std::cout << aa->SayHello() << std::endl;

    std::cin.get();

    return 0;
}

Update

The exact error I get using VS 2008 is (in German, sorry was not my choice..)

1>------ Erstellen gestartet: Projekt: ReflectiveFactory, Konfiguration: Debug Win32 ------
1>Kompilieren...
1>main.cpp
1>.\main.cpp(82) : error C2248: "AbstractFactory<BASE_T>::createInstance": Kein Zugriff auf protected Member, dessen Deklaration in der AbstractFactory<BASE_T>-Klasse erfolgte.
1>        with
1>        [
1>            BASE_T=Base
1>        ]
1>        .\main.cpp(55): Siehe Deklaration von 'AbstractFactory<BASE_T>::createInstance'
1>        with
1>        [
1>            BASE_T=Base
1>        ]
1>.\main.cpp(83) : error C2248: "AbstractFactory<BASE_T>::createInstance": Kein Zugriff auf protected Member, dessen Deklaration in der AbstractFactory<BASE_T>-Klasse erfolgte.
1>        with
1>        [
1>            BASE_T=Base
1>        ]
1>        .\main.cpp(55): Siehe Deklaration von 'AbstractFactory<BASE_T>::createInstance'
1>        with
1>        [
1>            BASE_T=Base
1>        ]
Community
  • 1
  • 1
Nils
  • 13,319
  • 19
  • 86
  • 108

1 Answers1

4

The Create method must be accessible to end users, which means that either that method is public in AbstractFactory or it is moved to Factory and made public there. The rest of the code in AbstractFactory can be protected.

It seems that only gcc accepts the code (clang++, comeau reject it) and it could be rightfully rejected (I would have to look into the standard). At any rate a simple workaround would be to create a helper function that will provide the member function pointer for you:

template <typename B>
template <typename D>
AbstractFactory<B>::func_ptr_type AbstractFactory<B>::getCreateInstancePtr() const {
   return &AbstractFactory<B>::createInstance<D>;
}

This template member method of the AbstractFactory template can be protected, as you are directly calling it on your own base in the actual Factory:

Factory::Factory() {
        m_map["DerivedA"] = getCreateInstancePtr<DerivedA>();
        m_map["DerivedB"] = getCreateInstancePtr<DerivedB>();
}

After checking with the standard:

11.5p1 [class.protected]

Except when forming a pointer to member (5.3.1), the access must be through a pointer to, reference to, or object of the derived class itself (or any class derived from that class) (5.2.5). If the access is to form a pointer to member, the nested-name-specifier shall name the derived class (or any class derived from that class).

That is, the expression m_map["DerivedA"] = &AbstractFactory::createInstance<DerivedA>; is incorrect, while m_map["DerivedA"] = &Factory::createInstance<DerivedA>; is correct (regarding access specifiers, but not types, the left hand side is an B* (AbstractFactory<B>::*)( std::string ) and the right hand side is a B* (Factory::*)( std::string ), so the assignment would fail.

This plays nice with the semantics of protected everywhere else, and in particular with not being able to access the protected members of other than your own base subobject:

class base {
protected:
   int x;
};
struct derived : base {
   static void f( base& b ) {
      b.x = 5;                 // Error: cannot access base::x from this context
   }
};
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Thx for your answer. At first I had exactly that, all methods protected except Create() public. However I got this error with VS 2008: error C2248: "AbstractFactory::createInstance" No access to member, etc – Nils Apr 17 '12 at 15:13
  • @Nils: The code should compile, this might be an issue with the compiler. Try creating a different equivalent test case where the base is not a template (i.e. manually create the equivalent of `AbstractFactory`) and see if the compiler still complains. – David Rodríguez - dribeas Apr 17 '12 at 15:29
  • yeah I think so.. will try at home with llvm and clang – Nils Apr 17 '12 at 15:32
  • @Nils: g++ 4.5 accepts the code, clang++ 3.0 and comeau rejects it with the similar error message... It makes some kind of sense (in the same sense that you cannot call a protected method on a reference to a base other than yourself (i.e. `protected` grants you access to your own `base`) – David Rodríguez - dribeas Apr 17 '12 at 15:41
  • Maybe those compilers try to close a loophole in the protected member mechanism that allows everyone to access protected members with the following legal C++03 code: #define ACCESS(A, M, N) struct N : get_a1::type { using get_a1::type::M; } \ template struct get_a1; template struct get_a1 { typedef A1 type; }; ACCESS((stack), c, get_c); int main() { stack p; p.push(10); p.push(11); cout << (p.*&get_c::c).front(); } – PlasmaHH Apr 17 '12 at 15:55
  • @PlasmaHH: The question is whether that code is actually legal in C++03 or not. That is, whether taking the address of the function is closer to *calling* the function on your base subobject or calling the function on a reference to a base that is not your own subobject. (and btw, there are ways of accessing all private and protected members legally in C++03 through the use of explicit template instantiation that do not involve inheritance...) – David Rodríguez - dribeas Apr 17 '12 at 16:30
  • @DavidRodríguez-dribeas: A comment here is probably too few space to prove that the above code is legal, but about 3 years ago we had a lengthy discussion in ##C++ of freenode and came to the conclusion that indeed this is legal and an unfortunate loophole (well, some people think this is the intended behaviour of protected) – PlasmaHH Apr 17 '12 at 19:09
  • @PlasmaHH: While the code that you present seems to be valid in C++03 (I have not been able to disprove it looking at the standard, but I am not fully convinced either) I don't think that it is related to the question. In your code, the derived type is bringing the base protected member into a public scope, and then obtaining the address of a public function, while in the question it is trying to obtain the address of a protected function in the base class. – David Rodríguez - dribeas Apr 17 '12 at 23:13