C++用可变参数存储回调

cld4siwp  于 2023-05-02  发布在  其他
关注(0)|答案(2)|浏览(121)

我真的需要你的帮助。因为我是一个初学者,我被困在存储回调与variadic参数。
目标是存储x个回调函数和它们的n个y类型的参数。我试着用模板,但坚持他们的演绎。回调需要在我现有的静态库中由其他事件触发,这些事件更改回调参数。所有的回调函数都将/必须返回void。
在我的main.cpp中,我需要:

void testFunc_i(int i){
    //do something
}
void subscribe_i(){
    myLib::registerCallback(
        "something", "INT",
        &testFunc_i
    );
}

void testFunc_ivf(int i, std::vector<float> f){
    //do something
}
void subscribe_ivf(){
    myLib::registerCallback(
        "another string",   "INT",
        "something different",  "FLOAT[]",
        &testFunc_ivf
    );
}
void testFunc_ifs(int i, float f, std::string s){
    //do something
}
void subscribe_ifs(){
    myLib::registerCallback(
        "foo",  "INT",
        "bar",  "FLOAT",
        "text", "STRING",
        &testFunc_ifs
    );
}
int main(int argc, char** argv)
{
    subscribe_i();
    subscribe_ivf();
    subscribe_ifs();
}

我在myLib中的内容是这样的:

namespace myLib{

    class Subscriber
    {
    public:
        Subscriber();
        ~Subscriber();
        void addString(const std::string &s)
        {
            v.push_back(s);
        }
        void addType(const std::string &s)
        {
            t.push_back(s);
        }
        void setCallback(callback_t callbackFn) //???
        {
            callback = callbackFn; // ???
        }

        void call( /* ??? */ )
        {
            // ???
            callback(); // ???
        }
    private:
        callback_t callback; // ???

        std::vector<std::strings>v;
        std::vector<std::strings>t;
    };

    // if argument is a string
    void add(unsigned &cnt, auto* subscriber, const std::string &value)
    {
        if (cnt%2==0) {
            subscriber->addString(value);
        }else{
            subscriber->addType(value);
        }
        cnt++;
    }

    // if argument is a function
    template <typename... Args>
    void add(unsigned &cnt, auto* subscriber, void(*_callbackFn)(Args...) )
    {
        subscriber->setCallback(_callbackFn);
    }

    template <typename... Ts>
    void registerCallback(Ts&&... ts)
    {
        auto subscriber = new Subscriber();
        unsigned cnt = 0;
        auto dummy = {(add(cnt,subscriber,ts), 0)...};
        Store::getInstance().addSubsriber(subscriber); //store the subscriber object in a Singleton (working)
    }
}
icnyk63a

icnyk63a1#

你不能这样做。
如果在函数中使用可变参数,最好的办法是将这些参数转换为元组。
然而,一个元组与具有任何不同参数列表的任何其他元组都不同,因此您不能将其存储在容器中以便稍后回调。
如果元组是从一个公共基类派生的,那么您就可以在使用之前存储它们(这必须通过一个virtual call()方法进行双重分派)。这样写起来不太方便,因为这意味着几乎要重写std::tuple类型。
如果我是你,我会做相反的事情,也就是说,做一个lambda来调用传递的回调函数,并将它转换为std::function存储在容器中,以备以后调用。
沿着伪代码(未测试):

template <typename F, typename... Args>
   void registerCallback(F f, Args&&... args)
   {
      // Make a function that's calling the callback and store that instead
      std::function<void()> laterCB = [&] { f(args...); };
      // Simply store in your std::vector<std::function<void()>> container
      Store::getInstance().addSubscriber(laterCB);
   }

   void triggerCB()
   {
       for (auto i : Store::getInstance().getCB()) 
           *i();
   }
  • 另一句话:*

这种代码总是非常不安全的,因为参数的类型可以是引用(语法中没有任何东西可以阻止这种情况)。当稍后回调时,引用的对象可能不再存在。如果你不得不写一个库,你最好避免这样做,相反,让你的接口非常严格,以防止这种情况。
不安全使用示例:

// DON'T DO THAT
struct A { ... };
int processA(A & a) { 
  a.foo();
}

if (something())
{
   A a;
   yourLib.registerCallback(processA, a);
}

// Calls processA with a reference on a 
// destructed object 'a' => so it'll crash
yourLib.triggerCB();
0qx6xfy6

0qx6xfy62#

你可以这样做。
希望有帮助:-)(我正在使用Qt,可以用std替换qt容器)

class CallbackLambda {

public:
    template<typename F>
    void store_lambda(const QString &key, F &&f) {
        using arg_tuple = typename function_traits<F>::arg_tuple;
        using return_type = typename function_traits<F>::return_type;

        myHash[key] = [f = std::forward<F>(f)](std::any args) -> std::any {
            if constexpr (std::is_same_v<return_type, void>) {
                std::apply(f, std::any_cast<arg_tuple &>(args));
                return {};
            } else {
                return std::apply(f, std::any_cast<arg_tuple &>(args));
            }
        };
    }

    template<typename... Args>
    std::any call(const QString &key, Args... arg) {
        return call_lambda(myHash[key], arg...);
    }
private:
    QHash<QString, std::function<std::any(std::any)>> myHash;

    template<typename T>
    struct function_traits : public function_traits<decltype(&T::operator())> {
    };

    template<typename ClassType, typename ReturnType, typename... Args>
    struct function_traits<ReturnType(ClassType::*)(Args...) const> {
        using arg_tuple = std::tuple<Args...>;
        using return_type = ReturnType;
    };

    template<typename... Args>
    std::any call_lambda(const std::function<std::any(std::any)> &f, Args &&... args) {
        return f(std::make_tuple(std::forward<Args>(args)...));
    }

};

int main() {
    auto manager = CallbackLambda();
    manager.store_lambda("print", []() {
        std::cout << "Hello, world!\n" << std::endl;
    });

    manager.store_lambda("int", [](int x, int y)  {
        std::cout << "The sum of " << x << " and " << y << " is " << (x + y) << std::endl;
        return x + y;
    });

    manager.store_lambda("float", [](float val, float v) {
        std::cout << "Message: " << val << " " << v << std::endl;
    });

    manager.store_lambda("stdString", [](std::string message) {
        std::cout << "Message: " << message << "\n" << std::endl;
    });
    manager.store_lambda("qString", [](QString message) {
        std::cout << "Message: " << message.toStdString() << "\n" << std::endl;
    });
    manager.store_lambda("char", [](const char *message) {
        std::cout << "Message: " << message << "\n" << std::endl;
    });
    manager.store_lambda("double", [](double val, double v) {
        std::cout << "Message: " << val << " " << v << std::endl;
    });

    manager.call("print");
    auto out = manager.call("int", 10, 20);
    std::cout << std::any_cast<int>(out) << std::endl;

    return 0;
}

欢迎来电。
您可能希望编辑call来检查回调是否存在,否则返回nullptr或类似的东西。..
C++存储带有可变参数的回调并获取返回

相关问题