0

I have a base class which a large number of my classes are derived from e.g.:

class BaseSystem
{
public:
   virtual void doThing() = 0;
}

I want to be able to some how tag all derived types of the class so I can find them when the application starts up. In c# I would do this by reflection either looking for an attribute or just looking for anything derived from the base class.

Is there a similar way I can do this in c++ where I can make classes with something and have them discovered at compile time and have an instance created for them all in a vector?

Edit: More of a context of what problem I am trying to solve.

I am creating a static library that allows a programmer to implement the Entity Component Systems pattern to form the basic guts of a game engine. The idea is the library has a base class which they can implement systems from and the system manager will be able to discover them and then run them when the game starts.

Wil
  • 534
  • 4
  • 18
  • So you want to have a `std::vector` which has one object of every derived class? Why? – Rakete1111 Sep 16 '18 at 13:23
  • 6
    This sounds like an XY problem. If you had such a vector of instances, what would you do with it? – melpomene Sep 16 '18 at 13:24
  • The simple answer is "no". There's no way to list classes or class members in C++. This is basically one of the many uses of reflection, which C++ doesn't have. You have to list those classes manually. – Not a real meerkat Sep 16 '18 at 13:28
  • Maybe [std::is_base_of](https://en.cppreference.com/w/cpp/types/is_base_of) can help you..? – Jesper Juhl Sep 16 '18 at 13:29
  • There's a [proposal](http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0194r3.html) for compile-time reflection that's being studied, though, but don't expect it to be available anytime soon. – Not a real meerkat Sep 16 '18 at 13:32
  • 2
    As per melpomene's comment, this looks like an XY problem - you want to do X, think that Y will achieve X, but you don't know how to do Y so ask how to do it. XY problems are frustrating for people, since Y is often unachievable and also pointless to people who don't understand X. So try describing the ACTUAL problem (i.e. X) that you want to solve - there may be useful solutions that don't rely on solving the spurious problem (Y) you have asked about. – Peter Sep 16 '18 at 13:44
  • This may be of interest to you (https://stackoverflow.com/questions/10332725/how-to-automatically-register-a-class-on-creation) - I think the technique here should be enough to get you started. – StoryTeller - Unslander Monica Sep 16 '18 at 13:52
  • might interest you: [building-and-accessing-a-list-of-types-at-compile-time](https://stackoverflow.com/questions/18701798/building-and-accessing-a-list-of-types-at-compile-time) – Jarod42 Sep 16 '18 at 16:59
  • @Peter you are right I should have posted more information on the problem I am trying to solve. I will edit my question above. – Wil Sep 17 '18 at 10:07

1 Answers1

2

As others have pointed out in the comments to your question, in general it is not possible to detect all existing derived classes of a particular base class at compile time with C++.

But if all you need is a mechanism to avoid having one single place knowing about all existing derived classes there is something you can do, albeit not at compile time.

Basic idea

The idea is to use initialization of static member variables (which is guaranteed to happen before main is executed) to register the derived classes in a common registry.

Such a registry can look like this:

class derived_registry
{
public:
    static std::size_t number_of_instances()
    {
        return _instances.size();
    }

    static base* instance(std::size_t const index)
    {
        assert(index < _instances.size());
        return _instances[index].get();
    }

    template <typename T, std::enable_if_t<std::is_base_of_v<base, T>, int> = 0>
    static std::size_t register_derived_class(std::unique_ptr<T> instance)
    {
        auto const index = _instances.size();
        _instances.emplace_back(std::move(instance));
        return index;
    }

private:
    static std::vector<std::unique_ptr<base>> _instances;
};

inline std::vector<std::unique_ptr<base>> derived_registry::_instances;

Any derived class of base now has to register itself by calling derived_registry::register_derived_class to initialize a static member variable, e.g. like so:

// in derived1.h
class derived1 : public base 
{
public:
    derived1();

    void do_something() override;

private:
   static std::size_t _index;
};

// in derived1.cpp
std::size_t derived1::_index = derived_registry::register_derived_class(std::make_unique<derived1>());

derived1::derived1()
    : base{} 
{
}

void derived1::do_something()
{
    std::cout << "derived 1\n";
}

This also shows why it is important to define the derived_registry::_instances vector as an inline variable: We need to be sure that derived_registry::register_derived_class is only called after derived_registry::_instances is already initialized. The easiest way to do so is to use the fact that when there is more than one variable with static storage duration defined in one translation unit they are guaranteed to be initialized in the order in which they were defined. Since we've defined derived_registry::_instances in the header file, we're guaranteed that derived_registry::_instances is initialized before derived1::_index, and thus before the call of derived_registry::register_derived_class.

You can see this approach in action on wandbox.

Making it fool-proof

While the implementation above works, it is rather cumbersome and there still is the possiblity that someone adds a new derived class but forgets to register it.

To ease the registration part you could use the CRTP pattern as described in the answer to this question that StoryTeller linked in a comment to your question.

While this can make the registration significantly easier, the registry still only works correctly when every derived class either inherits from the CRTP base or implements the registration itself, which is easy to forget. To ensure that there is no other choice but to inherit from the CRTP base class you can additionally make the constructor of base private and make the CRTP base-class the only friend. Then one can't accidentally inherit directly from base without registering the new derived class.

Corristo
  • 4,911
  • 1
  • 20
  • 36
  • I'm not sure this is actually portable. Static members aren't "guaranteed to be initialised before `main`". Rather, and loosely speaking, they are only guaranteed to be initialised before any other entity from the TU where it appears is odr-used, and so if you never have a code dependency on some module that you link in, there's not standard requirement that global initializers are called. It might work in practice, but I don't think you can guarantee this based on the standard. See http://eel.is/c++draft/basic.start.dynamic#4. – Kerrek SB Sep 16 '18 at 21:28
  • @KerrekSB You're right, I misremembered the guarantees for static intialization given in [basic.start.static] http://eel.is/c++draft/basic.start#static-1 to be given for all kinds of initializations of variables with static storage duration. The rationale for marking `_instances` as `inline` was to prevent static initialization order fiasco. – Corristo Sep 16 '18 at 22:22
  • @KerrekSB But I think this might still work according to the standard since luckily we're dealing with derived classes that themselves define overrides of virtual member functions, which - if I read http://eel.is/c++draft/basic.def.odr#7 correctly - already are ODR-used just by being defined in that TU. Therefore there is another entity in the TU that defines the static member variable `_index` which is ODR-used, and thus `_index` gets initialized. – Corristo Sep 16 '18 at 22:28
  • Awesome thank you for the detailed explanation. This solves my problem :) – Wil Sep 17 '18 at 10:06