кэшировать базовый тип из шаблонной функции для использования в std :: is_base_of <Here!,> - PullRequest
0 голосов
/ 23 января 2019

Я хочу создать библиотеку, которая: -

  1. Пользователь добавляет обратный вызов через addCallback<Base>(Callback* callback) (обычно с первого шага)
  2. Позже, обычно в другом .cpp, всякий раз, когда пользователь звонит actor<Bx>(): -

    • если Bx наследовать от Base, позвоните callback->callback()
    • иначе ничего не делать
  3. (Информация) Я точно знаю, что каждый Bx всегда наследуется от A.

Вот исходный код: -

#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
}

Как это сделать?

Мои плохие решения

Решение 1: std :: is_base_of

Я действительно люблю использовать std::is_base_of<Base,Derive>.
Однако это невозможно, потому что пользователи хотят вызывать только один тип Bx в actor<Bx>() для удобства.
std::is_base_of нужно имя двух классов, а не одного.

Решение 2 ( MCVE demo ): виртуальный деструктор + std :: function

Можно оптимизировать больше, но я хочу, чтобы все было просто: -

#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

Работает, но есть некоторые недостатки: -

  • Мне нужно добавить виртуальный деструктор к A, чтобы сделать его полиморфным . Я чувствую, что это противный взлом.
  • В моей игре, с некоторыми временными шагами, A::~A() потенциально вызывается> 100 000 раз в секунду.
    Я могу уменьшить стоимость, сделав B1 и C final и выполнить пакетное удаление через производный класс, но в некоторых местах это не подходит и неудобно.
  • Мне нужно создать экземпляр Bx только для проверки dynamic_cast .
    Это может вызвать некоторые сложности, если его конструктор делает что-то особенное.

Есть ли лучший способ?

1 Ответ

0 голосов
/ 24 января 2019

Можете ли вы попросить пользователя указать набор разрешенных типов Base?В этом случае задача становится простой ( онлайн-демонстрация ):

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>(), ...);
  }
};

Разрешенные типы Base регистрируются следующим образом:

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

Использование читает:

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>();

В качестве альтернативы API может быть разработан наоборот: вместо ограничения классов Base пользователь может указать все классы, которые разрешены в качестве аргумента шаблона actor.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...