9

Normally, if you know all the types you intend to create before hand, you can just do something like this:

typedef enum{
    BASE_CREATURE_TYPE = 0,
    ANIMAL_CREATURE_TYPE,
    ...
}CREATURE_TYPES

But this becomes tedious, because each time you create a new class, you need to update the enum. Also, the CREATURE_TYPES is still just items in an enum - how to tie to an actual class ?

I was wondering if there was some way, I could just write the classes, and at run time, without actually instantiating an object, create a set containing all the types.

Is this possible in C++? In Java there is something called "static blocks", which are executed when the class is loaded by the JVM.

EDIT: This question is not about static blocks - It is just an example - I am wondering if there is some way, that I can execute a method or block of code so I know what classes exist at runtime, without actually creating an object

EDIT: I meant set of all types, not "maps", so I can create an object of each type, without having to maintain a list.

EDIT: The reason I want this, is because I am trying to create a function that can call methods on all derived classes that are part of the application. For example, say I have several classes which all derive from class Foo, and have a balls():

Foo{
   balls();
}

Boo : public Foo{
   balls();
}

Coo: public Foo{
   balls():
}

At run time, I would like to know about all the derived classes so I can call:

DerivedClass:balls();

EDIT: Note, that I do not need to know about all the members of each derived class, I just want to know what all the derived classes are, so I can call balls(), on each of them.

EDIT: This question is similar: How to automatically register a class on creation

But unfortunately, he is storing an std::string(). How does one refer to the actual class ?

EDIT: In Smeehey's answer below, in the main method, how would I actually create an instance of each class, and call both static and non-static methods ?

Community
  • 1
  • 1
Rahul Iyer
  • 19,924
  • 21
  • 96
  • 190
  • 2
    "Create a map"? What kind of map? The map to Luke Skywalker? – Kerrek SB Jun 10 '16 at 11:20
  • Can you explain why you want this? It'll probably be easier to answer that way. – Shay Nehmad Jun 10 '16 at 11:20
  • @ShayNehmad I want it so that I can dynamically populate a menu, which is used to create the various derived types. But it would be very tedious to tie a button to each derived type, so I was trying to determine if there is any way to "reflect" all the derived classes that are part of the application so I can dynamically call all the methods, without knowing about each of the types. – Rahul Iyer Jun 10 '16 at 11:26
  • I think that it's possible with preprocessor commands or even a dedicated script. Or external DLLs, like a plug-in subsystem. Should I elaborate or is this not what you're looking for? – Shay Nehmad Jun 10 '16 at 11:30
  • @ShayNehmad - I would like to avoid having to write a build script, since that defeats the purpose of what I am trying to do. Can you explain about how I can do this with preprocessor commands ? – Rahul Iyer Jun 10 '16 at 11:31
  • A class can register itself with a class factory. The registration can be carried out by a constructor of a static object. You will need to declare a static object per derived class, or use some template magic (CRTP) to declare one automatically. – n. m. could be an AI Jun 10 '16 at 11:34
  • @n.m. Can you provide an example of how to do this with a static object? I am reading about CRTP but it is hard to understand. – Rahul Iyer Jun 10 '16 at 11:37
  • "without actually instantiating an object, create a set containing all the types." sounds a lot like the type registration used by Boost Serialization. But even if you had something that knew the types of all the derived classes the only thing it can do is call static functions because if the function isn't static you would need to know the this pointer as well. Are the functions that you will call static? – Jerry Jeremiah Jun 10 '16 at 11:45
  • @JerryJeremiah Not only do I want to call static functions, but I want to be able to instantiate objects as well. The CRTP question I linked to above is very close to what I want, but I don't have a way of referring to the "type", similar to what one would do in objective-c, to create an object without knowing about the type. – Rahul Iyer Jun 10 '16 at 11:47
  • You can't refer to a class itself in C++ - C++ has no reflective powers. You need to encode the information you need yourself and refer to that using some kind of identifier. (The identifier is often a string because it's convenient and readable.) – molbdnilo Jun 10 '16 at 11:52
  • Just out topic: **don't `typedef`** `struct`, `class`, `enum`, `union` in [tag:c++], it's an [unnecessary burden](http://stackoverflow.com/a/15501746/499359). – PaperBirdMaster Jun 10 '16 at 12:29
  • Do not use UPPERCASE identifiers for non preprocessor symbols if you do not want to get hard to catch problems – Slava Jun 10 '16 at 13:52
  • Unfortunately, you do not appear to have the problem you are trying to describe. The function foo() you described is an instance method, callable only on instances, and not statically. – KevinZ Jun 15 '16 at 19:25

3 Answers3

4

You could create a static registry for all your classes, and use a couple of helper macros to register new types within it. Below is a basic working demonstration, which creates 2 derived classes from Base. To add new classes you just use the two macros shown - one inside and one outside the class. Note: the example is very bare-bones and doesn't concern itself with things like checking for duplicates or other error conditions to maximise clarity.

class BaseClass
{
};

class Registry
{
public:
    static void registerClass(const std::string& name, BaseClass* prototype)
    {
        registry[name] = prototype;    
    }

    static const std::map<std::string, BaseClass*>& getRegistry() { return registry; };

private:
    static std::map<std::string, BaseClass*> registry;
};

std::map<std::string, BaseClass*> Registry::registry;

#define REGISTER_CLASS(ClassType) static int initProtoType() { static ClassType proto; Registry::registerClass(std::string(#ClassType), &proto); return 0; } static const int regToken;
#define DEFINE_REG_CLASS(ClassType) const int ClassType::regToken = ClassType::initProtoType(); 

class Instance : public BaseClass
{
    REGISTER_CLASS(Instance)
};

DEFINE_REG_CLASS(Instance)

class OtherInstance : public BaseClass
{
    REGISTER_CLASS(OtherInstance)
};

DEFINE_REG_CLASS(OtherInstance)

int main()
{
    for(auto entry : Registry::getRegistry())
    {
        std::cout << entry.first << std::endl;
    }
    return 0;
}

The above registers prototypes of the derived classes, which could be used for copy-constructing other instances for example. As an alternative, requested by the OP, you can have a system where factory methods are registered instead of prototypes. This allows you to create instances using a constructor with any particular signature, rather than the copy constructor:

class BaseClass
{
};

class Registry
{
public:
    using factoryMethod = BaseClass* (*)(int a, int b, int c);

    static void registerClass(const std::string& name, factoryMethod meth)
    {
        registry[name] = meth;    
    }

    static BaseClass* createInstance(const std::string& type, int a, int b, int c)
    {
        return registry[type](a, b, c);
    }

    static const std::map<std::string, factoryMethod>& getRegistry() { return registry; };

private:
    static std::map<std::string, factoryMethod> registry;
};

std::map<std::string, Registry::factoryMethod> Registry::registry;

#define REGISTER_CLASS(ClassType) static BaseClass* createInstance(int a, int b, int c)     \
                                  {                                                         \
                                      return new ClassType(a,b,c);                          \
                                  }                                                         \
                                  static int initRegistry()                                 \
                                  {                                                         \
                                       Registry::registerClass(                             \
                                           std::string(#ClassType),                         \
                                           ClassType::createInstance);                      \
                                       return 0;                                            \
                                  }                                                         \
                                  static const int regToken;                                \

#define DEFINE_REG_CLASS(ClassType) const int ClassType::regToken = ClassType::initRegistry(); 

class Instance : public BaseClass
{
    Instance(int a, int b, int c){}

    REGISTER_CLASS(Instance)
};

DEFINE_REG_CLASS(Instance)

class OtherInstance : public BaseClass
{
    OtherInstance(int a, int b, int c){}

    REGISTER_CLASS(OtherInstance)
};

DEFINE_REG_CLASS(OtherInstance)

int main()
{
    std::vector<BaseClass*> objects;
    for(auto entry : Registry::getRegistry())
    {
        std::cout << entry.first << std::endl;
        objects.push_back(Registry::createInstance(entry.first, 1, 2, 3));
    }
    return 0;
}
Smeeheey
  • 9,906
  • 23
  • 39
  • I'm still trying to wrap my head around this :D. Its a lot to take in for a noob – Rahul Iyer Jun 10 '16 at 11:50
  • This is very similar to the "static blocks" in Java you refer to. There is a lot of boilerplate but it basically boils down to creating a static instance of each class and getting this static instance to register itself in the registry class. By the time `main` runs, all static instances will have been initialised and registered – Smeeheey Jun 10 '16 at 11:52
  • Is this only for VC++ ? – Rahul Iyer Jun 10 '16 at 11:54
  • But in Xcode ClassType gives an error as "Unknown type name" Is it part of the standard ? – Rahul Iyer Jun 10 '16 at 11:55
  • Do you mean Xcode IDE? IDE errors are not compiler errors, you need to compile it if you're not already. See me live demo above – Smeeheey Jun 10 '16 at 11:57
  • I'm actually building cross platform for iOS, Android, and Visual Studio - I have to use Xcode to build my app due to some other dependencies. It won't compile if there are errors, so I'm not sure what you mean. I'm using Apple LLVM 7.1 – Rahul Iyer Jun 10 '16 at 11:58
  • I'm saying are you actually getting this error when compiling, or is it just something the IDE is highlighting to you during editing? If its a compiler error, and you're really using the code exactly as above, then you've got a non-compliant compiler. If so, can you share the full error? – Smeeheey Jun 10 '16 at 12:00
  • Specifically, what lines did you add? – Smeeheey Jun 10 '16 at 12:08
  • none. Just copy-paste, compiled for -std=c++11 and added the needed includes(iostream, string & map). - OS: Ubuntu 14.04, compiler: GCC.4.9.2 – Heto Jun 10 '16 at 12:11
  • Sorry, that question was for @John – Smeeheey Jun 10 '16 at 12:12
  • @Smeeheey - it works without any changes. But the question I have is, how do I use this to create instances of the derived classes ? – Rahul Iyer Jun 10 '16 at 12:14
  • Declare them exactly as `Instance` and `OtherInstance` in my example code. Don't forget to include both macros `REGISTER_CLASS` and `DEFINE_REG_CLASS` as per my examples – Smeeheey Jun 10 '16 at 12:16
  • @Smeeheey what I mean is, for example, in the loop in the main(), how would you modify it to create an instance of each type ? How would you call static methods on each type ? – Rahul Iyer Jun 10 '16 at 12:17
  • Well you already have an instance of each type available to you, the prototypes. They are accessible in that main loop through `entry.second` (which is a pointer). Is that what you mean? – Smeeheey Jun 10 '16 at 12:20
  • I don't understand - normally to create an instance, I do something like Foo foo = new Foo(a,b);. But in your case you say you're already creating an instance. But then what happens if I have more complicated constructors ? What If I want to create more instances of the same type ? How do I refer to the type, to do something like DerivedClass boo = new DerivedClass(a,b,c), if all I know is that each derived class has a constructor that takes 3 arguments. – Rahul Iyer Jun 10 '16 at 12:24
  • Ok - I see I can access them through entry.second->functionName() etc. But I'm confused how I would create other new instances - It seems like I would have to write some kind of helper functions inside the instance to create a new instance of itself. Also, It means that the application cannot exist with zero instances of any of the derived classes. – Rahul Iyer Jun 10 '16 at 12:28
  • My code is supposed to be just a demonstration. With a little modification, it is easy to do the things you're talking about. See this [modified demo](http://coliru.stacked-crooked.com/a/462587ef3de988f5). Here, instead of registering prototypes, we register a factory method instead. Then, given a compiler signature (see example classes), you can create new instances as it does in `main`. – Smeeheey Jun 10 '16 at 12:39
  • If you get this general idea, you should be able to easily do things like add new constructor types – Smeeheey Jun 10 '16 at 12:40
  • I also updated the answer with this alternative code – Smeeheey Jun 10 '16 at 12:51
  • @Smeeheey Thanks for your answers - I really appreciate it! I hope I didn't sound curt, was just trying to understand how it all works. Mind-blowing.. – Rahul Iyer Jun 10 '16 at 12:54
1

Use the CRTP design with interface for common "ancestor":

#include <vector>
#include <iostream>

/* Base */
struct IBase
{
    virtual void balls() = 0;
    virtual IBase *clone() const = 0;

private:
    static std::vector<IBase const *> _Derived;

public:
    static void
    create_all(void)
    {
    std::cout << "size: " << _Derived.size() << "\n";
        for (IBase const *a : _Derived)
        {
            IBase *new_object(a->clone());
            (void)new_object; // do something with it
        }
    }
};

std::vector<IBase const *> IBase::_Derived;

/* Template for CRTP */
template<class DERIVED>
class Base : public IBase
{
    static bool       created;
    static Base const *_model;

public:
    Base(void)
    {
        if (not created)
        {
            _Derived.push_back(this);
            created = true;
        }
    }
};

template<class DERIVED>
bool Base<DERIVED>::created = false;
template<class DERIVED>
Base<DERIVED> const *Base<DERIVED>::_model = new DERIVED;

/* Specialized classes */
struct Foo1 : public Base<Foo1>
{
    IBase *clone() const
    {
        std::cout << "new Foo1\n";
        return new Foo1(*this);
    }
    void balls() {}
};


struct Foo2 : public Base<Foo2>
{
    IBase *clone() const
    {
        std::cout << "new Foo2\n";
        return new Foo2(*this);
    }
    void balls() {}
};


int main(void)
{
    Foo1    a;
    IBase::create_all();
}

I tried this solution, but I do not know why the static Base const *_model; is not created when running the program.

Boiethios
  • 38,438
  • 19
  • 134
  • 183
1

You may use a global factory holding functions able to create objects (unique_ptr's) of derived classes:

#include <memory>
#include <unordered_map>
#include <typeinfo>
#include <typeindex>

// Factory
// =======

template <typename Base>
class Factory
{
    public:
    template <typename Derived>
    struct Initializer {
        Initializer() {
            Factory::instance().register_producer<Derived>();
        }
    };
    typedef std::function<std::unique_ptr<Base>()> producer_function;
    typedef std::unordered_map<std::type_index, producer_function> producer_functions;

    static Factory& instance();

    void register_producer(const std::type_info& type, producer_function producer) {
        m_producers[std::type_index(type)] = std::move(producer);
    }

    template <typename Derived>
    void register_producer() {
        register_producer(
            typeid(Derived),
            [] () { return std::make_unique<Derived>(); });
    }

    producer_function producer(const std::type_info& type) const {
        auto kv = m_producers.find(std::type_index(type));
        if(kv != m_producers.end())
            return kv->second;
        return producer_function();
    }

    const producer_functions producers() const { return m_producers; }

    private:
    producer_functions m_producers;
};

template <typename Base>
Factory<Base>& Factory<Base>::instance() {
    static Factory result;
    return result;
}

// Test
// ====

#include <iostream>

class Base
{
    public:
    ~Base() {}
    virtual void print() = 0;
};

class A : public Base
{
    public:
    void print() override { std::cout << "A\n"; }
    static void f() {}
};
Factory<Base>::Initializer<A>  A_initializer;

class B : public Base
{
    public:
    void print() override { std::cout << "B\n"; }
};
Factory<Base>::Initializer<B>  B_initializer;

class C {};

int main()
{
    auto& factory = Factory<Base>::instance();

    // unique_ptr
    auto producerA = factory.producer(typeid(A));
    if(producerA) {
        auto ptrA = producerA();
        ptrA->print();
    }

    // shared_ptr
    auto producerB = factory.producer(typeid(B));
    if(producerB) {
        std::shared_ptr<Base> ptrB(producerB());
        ptrB->print();
    }

    // missing
    auto producerC = factory.producer(typeid(C));
    if( ! producerC) {
        std::cout << "No producer for C\n";
    }

    // unordered
    for(const auto& kv : factory.producers()) {
        kv.second()->print();
    }
}

Note: The factory does not provide means of calling static member functions without object.