Как хранить и вызывать позже функции-члены разных классов, которые наследуются от одного и того же интерфейсного класса - PullRequest
0 голосов
/ 06 ноября 2018

Я создаю приложение, которое может быть расширено несколькими плагинами, которые унаследованы от класса интерфейса (называемого AppInterface) и загружены системой загрузки плагинов Qt. Глобальный статический экземпляр класса, рассматриваемый как ядро, делается доступным для каждого плагина, используя dll.

Теперь для обмена данными между плагинами я планирую создать в ядре 2 функции: одну для хранения указателей на функции слушателя под ключом на карте, а другую для вызова хранимых функций. под ключ, с аргументами к ним.

std::map<char*,std::list<void*> > listenerMap;
template<typename... Args, typename T>
void listen(char* key,  T *inst, void (T::*listenerSlot)(Args...))
{
get the functor list with key from the listener map and append the functor.
}
template<typename ...Args> 
void broadcast (char* key, Args... args)
{
get the list with key from the listener map and invoke all the functors with the given arguments.
}

Я не нашел способа сохранить функтор вместе с его объектом класса и вызвать их позже. Параметры функтора не являются фиксированными, они будут варьироваться от функции к функции, если только они не находятся под одной и той же клавишей на карте слушателя.

Есть ли какие-либо изменения в структуре функций широковещания и прослушивания или лучшие идеи для вызова сохраненных функций-членов?

Или я могу использовать AppInterface, который наследуется каждым плагином?

Примечание: я решил не использовать систему слотов сигналов Qt, потому что я хочу, чтобы AppInterface был базовым классом вместо QObject, по нескольким причинам.

1 Ответ

0 голосов
/ 07 ноября 2018

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

Тогда, кажется, проще предоставить способ получения плагинов:

AppInterface* GetPlugin(const char* name);

и до плагина dynamic_cast его до того, который требуется, и вызывать напрямую любой метод на нем. Например:

struct SomePlugin : AppInterface
{
    // ...
    void print();
    int foo(int n);
};

struct MyPlugin : AppInterface
{
    // ...
    void bar() {
        auto* plugin = dynamic_cast<SomePlugIn>(GetPlugin("SomePlugIn"));

        if (plugin) {
             plugin->print();
             int n = plugin->foo(42);
             // ...
        }
    }
};

С некоторыми соглашениями, как static const char* Plugin::Name, вы можете предоставить dynamic_cast в функции напрямую:

template <typename Plugin>
Plugin* GetPlugin() { return dynamic_cast<Plugin>(GetPlugin(Plugin::Name)); }

Если вы действительно хотите, чтобы ваш интерфейс регистрировал функции и вызывал их, вы можете использовать std::any: /

std::map<std::string, std::any> listenerMap;

template<typename Sig>
void register(const char* key, std::function<Sig> f);
{
    listenerMap[key] = f;
}

template<typename Sig, typename ...Args> 
void broadcast(const char* key, Args&& ... args)
{
     auto it = listenerMap.find(key);
     if (it != listenerMap.end()) {
         auto* f = std::any_cast<std::function<Sig>>(&it->second);
         if (f) {
              (*f)(std::forward<Args>(args)...);
         }
     }
}

При использовании, аналогичном:

struct MyPlugin : AppInterface
{
    // ...
    void print() const;
    void foo(int) const;

    void my_register() {
         register<void()>("my_print", [this](){ this->print(); });
         register<void(int)>("foo", [this](int n){ this->foo(n); });
    }

    void call_me() {
        broadcast<void()>("my_print");
        broadcast<void(int)>("foo", 42);
    }

};

Вы должны передать Sig в broadcast и не допускать удержания из Args в целях безопасности. Передача std::string& вместо const std::string& или const char* вместо std::string приведет к ожидаемому изменению std::function<void(const std::string&)> к std::function<void(std::string&)>, поэтому std::any_cast завершится неудачей.

...