【问题标题】:cache a Base type from template function to use in std::is_base_of<Here!,>从模板函数缓存一个 Base 类型以在 std::is_base_of<Here!,> 中使用
【发布时间】:2019-01-23 04:42:58
【问题描述】:

我想创建一个库:-

  1. 用户通过addCallback&lt;Base&gt;(Callback* callback)添加回调(通常在第一个时间步)
  2. 以后,通常在不同的.cpp,每当用户调用actor&lt;Bx&gt;() :-

    • 如果Bx继承自Base,调用callback-&gt;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&lt;Base,Derive&gt;
但是,这是不可能的,因为为了方便,用户只想在actor&lt;Bx&gt;() 中调用单个类型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

它有效,但有一些缺点:-

  • 我必须将 virtual destructor 添加到 A 以使其 多态。我觉得这是一个讨厌的黑客。
  • 在我的游戏中,在某些时间步长中,A::~A() 每秒可能被调用 >100,000 次。
    我可以通过 make B1C final 来降低成本,并通过派生类进行批量删除,但在某些地方,它不合适且不方便。
  • 我必须为 dynamic_cast 检查创建 Bx 的实例。
    如果它的构造函数做一些特殊的事情,这可能会导致一些复杂性。

有没有更好的方法?

【问题讨论】:

  • 想到一个更糟糕的 hack:throwcatch 可能被滥用来在运行时测试非多态类之间的公共继承关系。如果你敢于尝试,你可以使用std::type_index 来缓存结果,并确保每个遇到的新类型只执行一次该步骤。
  • 类似的想法,是的,但是对于您的用例,throwcatch 需要在不同的函数中。

标签: c++ templates inheritance c++14 dynamic-cast


【解决方案1】:

您能否要求您的用户指定一组允许的Base 类型?在这种情况下,任务变得简单(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>(), ...);
  }
};

允许的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 可以设计成另一种方式:可以要求用户指定所有允许作为 actor 模板参数的类,而不是限制 Base 类。

【讨论】:

  • 我盯着代码看了一会儿。很难测试,能否提供一些MVCE(例如coliru.stacked-crooked.com)?它看起来很有用。谢谢。
  • @cppBeginner:添加了在线演示的链接(请参阅我的回答的第一段)。单例样式可能是糟糕的设计,但约束类型的想法也可以应用于非单例:CallbackSystem&lt;A, B&gt; callbacks{}; callbacks.add_callback&lt;A&gt;(&amp;call1); /* ... pass instance around (by reference)... */ callbacks.actor&lt;C&gt;();
  • 在您答案的第一个版本(我更喜欢)中,用户必须额外输入。它有点容易出错并且不易维护。 (不过,它比 CRTP 更干净、更好。)在这个版本中,用户必须使用 correct 回调来调用 actor,例如CallbackSystem&lt;A, B&gt;::actor()。尽管有MyCallbacks 别名,但回调的用处会降低。顺便说一句,我使用单例来简化 MCVE。我实际上并没有使用该模式。谢谢。 :)
  • @cppBeginner:是的,我同意我的original answer 中的建议(用户必须指定每个类的基数)容易出错。此外,为了收集这些用户提供的信息,还需要某种程度的元编程。我也同意如果使用CallbackSystem&lt;A, B&gt;::actor&lt;C&gt;() 而不是MyCallbacks::actor&lt;C&gt;();,当前版本很脆弱。请注意,如果在非单例实例中处理回调,这不是问题:void do_stuff(MyCallbacks&amp; callbacks) { actor&lt;C&gt;(callbacks); }.
猜你喜欢
  • 2016-04-12
  • 2017-06-24
  • 1970-01-01
  • 2020-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-24
  • 1970-01-01
相关资源
最近更新 更多