调用存储在地图中的模板化std ::函数

时间:2017-01-06 13:24:04

标签: c++ linux stl variadic-templates c++17

我试图创建一个存储信号的基类(以int的形式),并允许我稍后使用它们来调用派生类指定的函数。它们存储在unordered_map中,因为它正是我所需要的 - 1个信号= 1个功能(加上它快速)。

尽管如此,我在调用它们时遇到了一些麻烦,但却以下列形式出现了一个巨大的错误:

  

与'{std::unordered_map<int, std::function<void()>, std::hash<int>, std::equal_to<int>, std::allocator<std::pair<const int, std::function<void()> > > >::mapped_type {aka std::function<void()>}} (int&, int&)'

的来电不匹配

编译错误来自signals_[_sig](args...);

const int SIGCHANGE = 1;

template<typename... Args>
class Signal {
private:
  std::unordered_map< int, std::function<void(Args...)> > signals_;

  inline void SetSignal(const int& _sig, std::function<void(Args...)> _func) {
    signals_[_sig] = _func;
  }

public:
  Signal() {}
  ~Signal() {}

  template<typename... Ts>
  void Sig(const int& _sig, Ts... args) {
    if (signals_.find(_sig) != signals_.end()) {
      signals_[_sig](args...);
    }
  }
};

class Obj : public Signal<> { 
private:
  int num_;

public:
  Obj* child;

  void Change(int _num) {
    num_ = _num;
    child->Sig(SIGCHANGE, _num);
  }

  void HandleChanged(int _num) {
    num_ = _num;
  }

};

int main() {
  Obj* obj1 = new Obj();
  Obj* obj2 = new Obj();

  obj1->child = obj2;

  obj1->Change(10);
}

此外,我不知道我是否正确使用模板 - 自从我上次使用它们以来已经很久了。每个功能都应该有自己的模板吗?如果我想将它用作成员变量,为什么我必须在课前指定模板?

1 个答案:

答案 0 :(得分:3)

由于您的函数有一个int参数,您需要

class Obj : public Signal<int>

而不是

class Obj : public Signal<>

后者创建了一个原型为void()的函数的地图,而您需要一个原型void(int)的函数。

另外,对Sig函数进行模板化是没用的,因为无论如何你都无法用Args之外的任何其他参数调用它。所以它可以简单地

  void Sig(const int& _sig, Args... args) {
    if (signals_.find(_sig) != signals_.end()) {
      signals_[_sig](args...);
    }
  }

如果您的目标是支持使用任意原型存储函数,那么确实需要以不同的方式完成,基本上通过运行时多态(地图中的所有项都需要具有相同的类型,因此类型本身需要在引擎盖下进行调用多态性。

修改

根据信号类型定义函数原型的一种可能性。要求信号整数始终作为编译时常量传递 - 特定信号类型的不同仿函数存储在std::tuple中:

const int SIGZERO = 0;
const int SIGCHANGE = 1;
const int SIGOTHER = 2;

// non-specialized template intentionally left empty
// (or can provide default prototype)
template<int SIG>
struct SignalPrototype;

template<>
struct SignalPrototype<SIGZERO>
{
    typedef std::function< void() > type;
};

template<>
struct SignalPrototype<SIGCHANGE>
{
    typedef std::function< void(int) > type;
};

template<>
struct SignalPrototype<SIGOTHER>
{
    typedef std::function< void(int, int) > type;
};


class Signal {
private:
    std::tuple<
        SignalPrototype<SIGZERO>::type,
        SignalPrototype<SIGCHANGE>::type,
        SignalPrototype<SIGOTHER>::type
    > signals_;

protected:

    template<int SIG>
    inline void SetSignal(typename SignalPrototype<SIG>::type _func) {
        std::get<SIG>(signals_) = _func;
    }

public:

    template<int SIG, typename... Ts>
    void Sig(Ts... args) {
        SignalPrototype<SIG>::type func = std::get<SIG>(signals_);
        if (func)
            func(args...);
    }

};

struct MyHandler : SignalPrototype<SIGCHANGE>::type
{
    void operator()(int x)
    {
        std::cout << "Called MyHandler with x = " << x << std::endl;
    }
};


class Obj : public Signal {
private:
    int num_;

public:
    Obj* child;

    Obj()
    {
        SetSignal<SIGCHANGE>(MyHandler());
    }

    void Change(int _num) {
        num_ = _num;
        child->Sig<SIGZERO>();
        child->Sig<SIGCHANGE>(_num);
        child->Sig<SIGOTHER>(1, 2);
    }

    void HandleChanged(int _num) {
        num_ = _num;
    }

};

(一般来说,最好让Signal成为Obj的成员而不是继承)