3

I want to create a library that :-

  1. User adds callback via addCallback<Base>(Callback* callback) (usually at first timestep)
  2. Later, usually in a different .cpp, whenever user call actor<Bx>() :-

    • if Bx inherit from Base, call callback->callback()
    • else do nothing
  3. (Information) I know for sure that every Bx always inherit from A.

Here is the initial code :-

#include <iostream>
class A{};
class Callback{
     public: virtual void callback()=0; 
};
template<class Base> void addCallback(Callback* callback){  
    //???
};
template<class Bx> void actor(){
    //???
}
//^^^^^^ my library end here

class B : public A{};
class B1 : public B{};
class C : public A{};
class CallbackCustom1: public Callback{
    public: virtual void callback(){std::cout<<"A"<<std::endl;}
};
class CallbackCustom2: public Callback{
    public: virtual void callback(){std::cout<<"B"<<std::endl;}
};
int main(){
    CallbackCustom1 call1;
    CallbackCustom2 call2;
    addCallback<A>(&call1); 
    addCallback<B>(&call2); 
    //vvv  below is usually in another .cpp
    actor<B1>(); // should print "A" and "B"
    actor<C>(); // should print "A" only
}

How to do it?

My poor solutions

Solution 1 : std::is_base_of

I really love to use std::is_base_of<Base,Derive>.
However, it is impossible because users want to invoke only a single type Bx in actor<Bx>() for convenience.
std::is_base_of need name of two classes not one.

Solution 2 (MCVE demo) : virtual destructor + std::function

It can be optimized more but I want to keep it simple :-

#include <iostream>
#include <functional>
class A{public: virtual ~A()=default; };
class Callback{
     public: virtual void callback()=0; 
};
class MyTuple{public:
    std::function<bool(A*)> func;
    Callback* callback;
};
std::vector<MyTuple> myTuples;
template<class Base> void addCallback(Callback* callback){  
    std::function<bool(A*)> func=
        [](A* a){return dynamic_cast<Base*>(a)!=nullptr;};
    MyTuple tuple; tuple.func=func; tuple.callback=callback;
    myTuples.push_back(tuple);
}
template<class Bx> void actor(){
    Bx b;
    for(auto tuple:myTuples){
        if(tuple.func(&b)){
            tuple.callback->callback();
        }
    }
}
//^^^^^^ my library end here

It works, but there are some disadvantages :-

  • I have to add virtual destructor to A to make it polymorphic. I feel that it is a nasty hack.
  • In my game, in some time-steps, A::~A() is potentially called >100,000 times per seconds.
    I can reduce the cost by make B1 and C final and do a batch deletion via derived class, but in some places, it is unsuitable and inconvenient.
  • I have to create instance of Bx just for the dynamic_cast check.
    This may cause some complication if its constructor do something special.

Are there any better way?

cppBeginner
  • 1,114
  • 9
  • 27
  • An even worse hack comes to mind: `throw` and `catch` could be abused to test for a public inheritance relationship between non-polymorphic classes at runtime. If you do dare to play with that, you can use some `std::type_index` to cache results and make sure that step is only done once per new type encountered. – aschepler Jan 23 '19 at 05:47
  • @aschepler like [Cassio Neri's solution](https://stackoverflow.com/a/18100934) in https://stackoverflow.com/questions/18099241/check-if-class-is-derived-from-a-specific-class-compile-runtime-both-answers-a ? – cppBeginner Jan 23 '19 at 05:53
  • A similar idea, yes, but for your use case, the `throw` and `catch` would need to be in different functions. – aschepler Jan 23 '19 at 06:18

1 Answers1

1

Can you require your user to specify the set of allowed Base types? In that case the task becomes simple (online demo):

static_assert(__cplusplus >= 201703L, "example for C++17, but C++14 possible");

#include <iostream>
#include <type_traits>
#include <vector>

struct Callback {
  virtual void callback() = 0;
};

template<class... RegisteredBases>
struct CallbackSystem {

  template<class Base>
  static auto& callbacks_for() {
    static std::vector<Callback*> callbacks_for_base_{};
//
// For each `Base`, the callbacks are stored in a different vector.
// This way, we can avoid the in-loop branch (see `actor_impl`).
//
// TODO: consider performance cost of bad memory locality (can be
// improved if necessary).
//
    return callbacks_for_base_;
  }

  template<class Base>
  static void addCallback(Callback* callback) {
    static_assert((... || std::is_same<Base, RegisteredBases>{}));
    callbacks_for<Base>().push_back(callback);
  }

  template<class Derived, class RegisteredBase>
  static void actor_impl() {// called from `actor` for each RegisteredBase
    if(std::is_base_of<RegisteredBase, Derived>{}) {// branch outside loop
      // if `RegisteredBase` matches then process all its callbacks
      for(Callback* callback : callbacks_for<RegisteredBase>()) {
        callback->callback();
      }
    }
  }

  template<class Derived>
  static void actor() {
    (actor_impl<Derived, RegisteredBases>(), ...);
  }
};

The allowed Base types are registered like this:

using MyCallbacks = CallbackSystem<A, B> {};

The usage reads:

MyCallbacks::addCallback<A>(&call1);
MyCallbacks::addCallback<B>(&call2);
// MyCallbacks::addCallback<B1>(&call2);// compile error (good)


//vvv  below is usually in another .cpp
std::cout << R"(should print "A" and "B":)" << std::endl;
MyCallbacks::actor<B1>();

std::cout << R"(should print "A" only:)" << std::endl;
MyCallbacks::actor<C>();

Alternatively, the API can be designed the other way around: Instead of restricting the Base classes one can require the user to specify all classes which are allowed as the template argument of actor.

Julius
  • 1,816
  • 10
  • 14