C ++ хранит обратные вызовы с аргументами variadi c - PullRequest
2 голосов
/ 24 января 2020

Мне действительно нужна твоя помощь. Будучи новичком, я застрял в хранении обратных вызовов с аргументами variadi c.

Цель состоит в том, чтобы хранить x обратных вызовов с n аргументами типа y. Я пробовал с шаблонами, но застрял на их удержании. Обратные вызовы должны быть вызваны позже в моей существующей библиотеке stati c другими событиями, изменяющими аргументы обратных вызовов. Все функции обратного вызова всегда будут / должны возвращать 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)
    }
}

1 Ответ

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

Вы не можете сделать это таким образом.

Если вы используете в функции аргументы variadi c, лучшее, что вы можете сделать, - это преобразовать эти аргументы в кортеж.

Однако один кортеж отличается от любого другого кортежа с любым другим списком аргументов, поэтому вы не можете сохранить его в контейнере для последующего обратного вызова.

Если кортежи были получены из Общий базовый класс, вы сможете хранить их до использования (это должно go через слой двойной диспетчеризации с virtual call() методом). Это не очень удобно писать, так как это подразумевает почти переписывание типа std::tuple.

На вашем месте я бы сделал наоборот, то есть сделал бы лямбду, которая вызывает обратный вызов, и передал он преобразован в 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(); 
...