【问题标题】:Vector with objects that have function pointers of varying type带有具有不同类型函数指针的对象的向量
【发布时间】:2019-04-13 23:08:24
【问题描述】:

我正在尝试实现一个向量,该向量表示从基类继承的 TimedCallback 对象列表。 它们包含一些基本变量,以及一个作为主要功能的函数指针。 该函数应该能够返回任何类型并具有任何参数。 我将它们作为 lambdas 传递给函数,到目前为止还没有任何问题。

这是相关代码:

std::vector<std::unique_ptr<TimedCallbackBase>> m_TimerCallbackList;

struct TimedCallbackBase {
    TimedCallbackBase() = default;
    virtual ~TimedCallbackBase() = default;

    template<typename T> T run()
    {
        return dynamic_cast< TimedCallback<T> & >(*this).Run();
    }
    template<typename T> std::string name()
    {
        return dynamic_cast< TimedCallback<T> & >(*this).Name;
    }
    template<typename T> TaskTimer time()
    {
        return dynamic_cast< TimedCallback<T> & >(*this).Time;
    }
    template<typename T> int repeatcount()
    {
        return dynamic_cast< TimedCallback<T> & >(*this).RepeatCount;
    }
};

template <typename Fu>
struct TimedCallback : TimedCallbackBase {
    TimedCallback(const std::string& name, const std::string& time, Fu f, int r) : Name(name), Run(f), Time(time), RepeatCount(r) {}
    std::string Name;
    Fu Run;
    TaskTimer Time;
    int RepeatCount;
};

template<typename Fu>
void Schedule(const std::string& name, const std::string& time, Fu f, int repeatCount = 0) {
    TimedCallback cb(name, time, f, repeatCount);
    if (!vec_contains(m_TimerCallbackList, cb)) {
        m_TimerCallbackList.push_back(cb);
    }
    else { Global->Log->Warning(title(), "Callback '"+name+"' already exists."); }
}

我的问题是这种方法。我无法正确运行函数指针。

void _RunTimers() {
    if (m_TimerCallbackList.size() > 0) {
        for (auto &t : m_TimerCallbackList) {
            if (t != nullptr) {

                std::string _name = t.get()->name(); // wrong syntax, or signature?
                TaskTimer  _time = t.get()->time();
                int  _repeatcount = t.get()->repeatcount();
                //auto _run = t.get()->run(); ??

                Global->t("Callback name: " + _name);
                Global->t("Callback time: " + _time.GetRealTimeAsString());
                Global->t("Callback count: " + its(_repeatcount));
            }
        }
    }
    else { Global->Log->Warning(title(), "No timed tasks to run at this time."); }
}

我打算使用这样的代码:

Task->Schedule("testTimer", "10sec", [&]{ return Task->Test("I'm a 10sec timer."); });

_RunTimers();

我觉得我离正确地做这件事还很远。 我不想为 _RunTimers(); 指定任何模板;方法。 请帮助我了解这怎么可能。

编辑:

我的意思是,我想完全可以按照以下方式定义一堆 typedef

using int_func = std::function<int()>;

对于每一种可能的情况,然后重载我的包装器对象,但我一直在寻找更动态和防更改的东西。

编辑 2:实施建议的更改后

注意:为了模棱两可,我重命名了这些方法。 (这里不包括Test()方法,只是对字符串参数做一个std::cout)

主要:

Callback myCallback = make_callback(&TaskAssigner::Test, "I'm a 5sec timer.");

Task->ScheduleJob("timer1", "5sec", myCallback, -1);
Task->RunScheduledJobs();

实用程序:

typedef double RetVal;
typedef std::function<RetVal()> Callback;

template <class F, class... Args>
Callback make_callback(F&& f, Args&&... args)
{
    auto callable = std::bind(f, args...);                  // Here we handle the parameters
    return [callable]() -> RetVal{ return callable(); };    // Here we handle the return type
}

struct TimedCallback  {
    TimedCallback(cstR name, cstR time, Callback f, int r)
        : Name(name), Run(f), Time(time), RepeatCount(r) {}

    RetVal operator()() const { return Run(); }

    bool operator==(const TimedCallback& other) {
        if (Name == other.Name && RepeatCount == other.RepeatCount) { return true; }
        return false;
    }

const bool operator==(const TimedCallback& other) const {
    if (Name == other.Name && RepeatCount == other.RepeatCount) { return true; }
    return false;
}

    std::string Name;
    Callback Run;
    TaskTimer Time;
    int RepeatCount;
};

TaskAssigner .h:

void ScheduleJob(const std::string& name, const std::string& time, const Callback& func, int repeatCount = 0);

void RunScheduledJobs();

std::vector<TimedCallback> m_TimerCallbackList;

TaskAssigner.cpp:

void TaskAssigner::ScheduleJob(const std::string& name, const std::string& time, const Callback& func, int repeatCount) {

    TimedCallback cb(name, time, func, repeatCount);

    if (!vec_contains(m_TimerCallbackList, cb)) {
        m_TimerCallbackList.emplace_back(cb);
    }
    else { Global->Log->Warning(title(), "Callback '" + name + "' already added."); }
}

void TaskAssigner::RunScheduledJobs() {
    if (m_TimerCallbackList.size() > 0) {
        for (auto &t : m_TimerCallbackList) 
        {
            RetVal value = t();
            //Global->t("Callback result: " + std::to_string(value));
            Global->t("Callback name: " + t.Name);
            Global->t("Callback time: " + t.Time.GetRealTimeAsString());
            Global->t("Callback count: " + its(t.RepeatCount));
        }
    }
    else { Global->Log->Warning(title(), "No timed tasks to run at this time."); }
}

当前问题:

编译器说:C3848: expression has type 'const std::_Bind, const char(&)[18]>' 会丢失一些 const-volatile 限定符以便调用 .....

我尝试进行研究,有些人提到了 VS 2013 中关于绑定和自动的错误。解决方案包括键入签名而不是自动签名,或者删除/添加适当的 const(?)。不确定这是否是同一个错误,或者我的实现是否仍然不正确。 (错误:https://stackoverflow.com/a/30344737/8263197

我不确定如何使 RetVal 支持使用此 typedef 的任何值。 我试过了

    template<typename T>
struct CallbackReturnValue {
    CallbackReturnValue(T v) : value(v) {}
    T value;
};

但是我仍然需要模板化其他支持方法。我在这里误会了什么?

【问题讨论】:

  • 您从未说明问题所在。这是编译问题吗?运行时问题?设计问题?
  • t.get()-&gt;name(); 不起作用,因为编译器无法推断出函数模板 template&lt;typename T&gt; std::string TimedCallbackBase::name(); 的模板参数。但是如果每个函数都只是转换为派生类,我真的看不出基类的意义。您是否这样做是为了摆脱模板参数以便能够存储在向量中?
  • 因为写出来的runtimers不需要返回值。
  • @PaulMcKenzie 请原谅我。这是一个编译问题。我无法理解我想要实现的法律语法是什么。
  • @Quimby 没错,我试图摆脱模板参数,以便不必像那样定义运行可回调对象列表的函数。

标签: c++ templates vector polymorphism


【解决方案1】:

您似乎正在尝试重新发明std::function(“通用多态函数包装器”)。而不是处理模板和多个子类,我会尝试类似以下的东西。

typedef std::function<void()> Callback;

struct TimedCallback {
    TimedCallback(const std::string& name, const std::string& time, const Callback & f, int r) :
        Name(name),
        Run(f),
        Time(time),
        RepeatCount(r)
    {}

    // Not wise to differentiate names by case (run vs. Run),
    // but this form might be instructive as to how this
    // setup would fit into your existing _RunTimers().
    // By the way, _RunTimers is a reserved identifier.
    void run()
    {
        Run();
    }

    std::string Name;
    Callback Run;
    TaskTimer Time;
    int RepeatCount;
};

如果您需要回调返回的值,则需要更复杂的方法。但是,请尝试一次迈出这一步。


经过一些澄清后,似乎目的是将返回值存储在某种容器中。为了使这项工作,需要有一种所有返回值都可以转换为的类型(例如让所有返回值都是从公共基类派生的类)。我将继续提供一种可行的方法。

第一步是定义通用返回类型。通过将其设为typedef,我可以在以后的代码中抽象出这个选择。

typedef /* fill this in */ RetVal;

接下来,我们修改Callbackrun() 的定义以考虑这种类型。

typedef std::function<RetVal()> Callback;

RetVal run()
{
    return Run();
}

TimedCallback 的定义在其他方面保持不变,但我会引入一个便利层,以便更轻松地构造这些回调。灵感来自“make_pair”和“make_tuple”等令人敬畏的名字:

template <class F, class... Args>
Callback make_callback(F&& f, Args&&... args)
{
    auto callable = std::bind(f, args...);               // Here we handle the parameters
    return [callable]() -> RetVal{ return callable(); }; // Here we handle the return type
}

啊,模板终于出现了!但请注意,模板已本地化为这个便利功能;如果您的可调用对象已经采用方便的形式,则不一定需要使用此模板。假设对象不是方便的形式,这里是一个使用方便的示例。例如,我将假设一个字符串可以隐式转换为RetVal

// Example function that takes a parameter.
std::string hello(const std::string & world)
{
    std::cout << "Hello " << world << "!\n";
    return world;
}

// Example main function to see that this can work.
int main()
{
    // Placeholder for m_TimerCallbackList.
    std::vector<TimedCallback> timer_list;

    // Instead of Task->Schedule(), I'll emplace on a vector.
    timer_list.emplace_back("testTimer", "10sec",
                            make_callback(hello, "Earth"),
                            1);

    // Instead of RunTimers(), I'll manually run the first callback.
    RetVal value = timer_list[0].run();
}

【讨论】:

  • 感谢您的评论。我的印象是,对于这个 typedef,我只能添加带有“void func()”签名的 TimedCallback 对象?我希望能够添加任何类型,例如 'bool func()' 或 'int func(string)' 等。如果我错了,请纠正我。
  • @MadsMidtlyng 既然您使用的是 lambda,为什么需要它们返回任何内容?运行计时器时,您没有使用返回值。至于参数,请参阅我的链接中的示例std::function,尤其是那些使用std::bind 的示例。它可以工作。 (也许如果您有更多需要处理的案例示例?)
  • 基本上,我想做的是定义一个自定义作业并将其添加到列表中。该作业具有计时器、名称和功能。作业是根据它们的时间循环运行的,所以如果函数有返回值,应该可以得到它。该函数应该是具有可变参数的任何类型,例如'ReturnType myFunc(Args...arguments)',所以如果传递的函数是'void func()' 或'int calcFunc(a, b)',它也可以工作。这样有意义吗?
  • @MadsMidtlyng 如何获取返回值? RunTimers 会知道如何处理返回的值吗?我不担心返回值,因为当你有回调时,通常没有很好的方法来利用返回值。
  • 我正在考虑将它们添加到另一个在 _RunTimers() 方法期间收集它们的向量中。我认为在我的情况下,返回值可以作为简单类型而没有自定义类。不可行吗?
猜你喜欢
  • 2017-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-31
相关资源
最近更新 更多