0

Assume I have a class foo with member function bar:

// foo.hpp
class foo
{
    ...
    virtual whatever_type bar() {...}
    ...
};

And class foo1 foo2 foo3 which is inherited from foo:

//foo1.hpp
class foo1 : public foo
{
    ...
    whatever_type bar() {...}
    ...
};
//foo2.hpp
class foo2 : public foo
{
    ...
    whatever_type bar() {...}
    ...
};
//foo3.hpp
class foo3 : public foo
{
    ...
    whatever_type bar() {...}
    ...
};

In the main function, if I want to create an object for each class foox, I have to write it manually.

// main.cpp
#include ...
int main()
{
    std::vector<foo> all_foo = {foo1(), foo2(), foo3()};
    for(auto foo_object : all_foo)
        foo_object.bar();
    ...
}

Is there any way to "register" a class when it is defined? So other functions will be able to get a list of all the registered classes.

Josiah
  • 205
  • 3
  • 13
陈浩南
  • 509
  • 2
  • 9
  • Nope. You need to implement by yourself or use the other library. There is no built-in feature for this. – UltimaWeapon Feb 25 '20 at 14:16
  • I don't know exactly what it is you are trying to achieve, but it almost looks like you're going about it the wrong way around. That said, I really can't suggest the best way to approach this without knowing a little more about the context of the problem. – Qubit Feb 25 '20 at 14:16
  • 11
    Unrelated to your question, but you've got a [slicing](https://stackoverflow.com/q/274626/5522303) problem here: `std::vector all_foo = {foo1(), foo2(), foo3()};` – Kevin Feb 25 '20 at 14:18
  • This feature is called [reflection](https://en.wikipedia.org/wiki/Reflection_(computer_programming)) (([this thread](https://stackoverflow.com/q/41453/69809)) mentions it in C++ context). It should be added to C++ in [future versions](https://cplusplus.github.io/reflection-ts/draft.pdf), right now you will probably have to resort to some template tricks. – Groo Feb 25 '20 at 14:23
  • @MarkLoeser: The critical difference is that those classes are unrelated. That makes it more complex. – MSalters Feb 25 '20 at 16:02
  • If Windows, you can use COM registration. – Michael Chourdakis Feb 25 '20 at 16:38

2 Answers2

1

Wrap foo in a CRTP base class:

template <typename T>
class foo_base : public foo

Add a static member variable to this class, and register the class in its initializer:

template <typename T>
void register_class()
{
    // Manually initialize `cout` and others.
    // This is needed only because we use `cout` in this function.
    // See https://en.cppreference.com/w/cpp/language/initialization#Non-local_variables
    // for why it might not work otherwise.
    std::ios::Init init;

    std::cout << __PRETTY_FUNCTION__ << '\n';
}

template <auto> struct value_tag {};

template <typename T>
class foo_base : public foo
{
    [[maybe_unused]] inline static int unused =
    []{
        register_class<T>();
        return 0;
    }();

    // Trick the compiler into instantiating `unused`.
    // Otherwise it would be ignored because it's not used anywhere else.
    [[maybe_unused]] value_tag<&unused> unused_func() {return 0;}
};

class foo1 : public foo_base<foo1> {};
class foo2 : public foo_base<foo2> {};

This way, register_class<T>() will be executed for every class derived from foo_base<T> when your program starts.

Be careful with what you do in register_class, since when it runs, some global variables may not be initialized yet. To work around that, make them static and wrap them in functions:

using type_map_t = std::map<std::string, std::unique_ptr<foo>(*)()>;

type_map_t &GetTypeMap()
{
    static type_map_t ret;
    return ret;
}

template <typename T>
void register_class()
{
    // For implementation of `TypeName<T>()` see
    // https://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template/59522794#59522794
    GetTypeMap().emplace(TypeName<T>(), []()->std::unique_ptr<foo>{return std::make_unique<T>();});
}
HolyBlackCat
  • 45,832
  • 5
  • 81
  • 134
0

The easiest way is to register "factories":

using Factory = std::function<std::shared_ptr<Foo>()>;
std::vector<Factory> factories = {
      []{return new Foo1;},
      []{return new Foo2;},
      []{return new Foo3;}
};

I've used shared_ptr<Foo> to prevent slicing, as noticed in the comments.

MSalters
  • 159,923
  • 8
  • 140
  • 320