Pull to refresh
6
0

Разработчик ПО

Send message

Вот несколько проблем, которые находятся на поверхности:

1. Привязка к адресу метода

В разных единицах компиляции функции и методы могут иметь разные адреса по разным причинам static, inline, специализация шаблонов и др. Проверка заняла 5 минут, ваш подход не работает.

Виртуальные методы не подерживаются.

Лямбды не поддерживаются.

Привязка методов с меньшим количеством параметров не поддерживается.

Использование std::bind невозможно.

2. Проблемы с производительностью

Многое можно перевести в constexpr.

Использование для активации сигнала virtual метода.

Лишние выделения памяти с помощью std::unique_ptr

3. Ошибки в коде

Отсутствие typename

Arg &&

Разная логика при удалении функции при активном и пассивном состоянии.

Когда встречается сочетание слов Event System первое что приходит на ум Event-driven architecture (EDA) и Event-driven programming. Ключевым моментом является наличие общего понятия Event - "сообщения, которое возникает в различных точках исполняемого кода при выполнении определённых условий". То есть Event - это не сущность вызывающая колбеки по подписке, а некоторая структура данных передаваемая между поведенческими сущностями для оповещении о любом событии, произошедшем с ними.

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

Можно использовать иерархию типов событий

struct Event {
  virtual ~Event() = default;
  virtual Type type () const = 0;
};

struct ConcreteEvent : Event
{
  // ...
};

Но тогда система событий будет ограничена только этой иерархией.

Либо в качестве события использовать std::any, тогда можно обмениваться более широким набором данных.

Для реализации событийной модели используется паттерн Observer (Publisher/Subscriber)

Простейшая реализация:

struct ISubsriber {
  using AnyEvent = std::any;
  
  virtual ~ISubsriber() = default;
  virtual onEvent (AnyEvent const & event) = 0;
};

struct Publisher {
  void addSubscriber(ISubscriber*);
  void removeSubscriber(ISubscriber*);
  
  void notify(AnyEvent const & event) const
  {
    for (auto & subscriber : subscribers_)
      subscriber->onEvent(event);
  }
};

Вся прелесть EDA в том, что компоненты на столько слабо связаны, что их можно соединять в любом сочетании, так как они используют обобщенное понятие Event.

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

По самой реализации отвечу в другом комментарии.

Описанное в статье не имеет ничего относящегося к Event System, а является некоторой скажем прямо не очень удачной реализацией механизма signal slot взаииодействия. О чем уже намекали в нескольких комментариях. Как реализация для изучения принципов взаимодействия подойдет, как инструмент для серьезной разработки - нет.

Ваше решение содержит недочеты и ошибки в реализации, исправление которых приведет к реализации @Kelbon.

Сталкивался с попдобным при использовании is_detected. Собственно интересная задача на тему ADL и правил инстанцирования шаблонов.

А если использовать в разных библиотеках не саму функцию, а только registry - он будет работать правильно.

Если передать из одной библиотеки registry сформированный в другой, то из-за несоответствия индексов в storage будет получен не тот вектор значений.

В представленном решении используется unordered_map<type_index, any>. Если использовать std::type_index или соответствующий hash_code, то таких проблем не будет.

При такой регистрации типа возможна очень нехорошая ситуация.

При сборке разных библиотек в каждой из них будет своя собственная последовательность индексов type_index, а при сборке приложения вообще другая.

Для примера можно реализовать foolib с методом

void foo ()
{
    ::std::cout << "double:" << type_index<double>::value() << ::std::endl;
    ::std::cout << "int:" << type_index<int>::value() << ::std::endl;
}

затем barlib с методом

void bar ()
{
    ::std::cout << "int:" << type_index<int>::value() << ::std::endl;
    ::std::cout << "double:" << type_index<double>::value() << ::std::endl;
}

а затем в тестовом приложении сделать так

void baz()
{
    ::std::cout << "string:" << type_index<::std::string>::value() << ::std::endl;
    ::std::cout << "vector<int>:" << type_index<::std::vector<int>>::value() << ::std::endl;
}

int main()
{
    foo();
    bar();
    baz();
    return 0;
}

и получить на выходе

double:0
int:1
int:0
double:1
string:0
vector<int>:1

А как же классический if constexpr? Или enable_if, или requires на сам метод test_running_time? Тогда и boid не потребовался бы. Или у этого типа есть ещё какой то смысл?

Так исторически сложилось. Такое соглашение о форматировании кода в большинстве проектов, в которых я участвовал. Похоже, Qt оказал влияние). Но всегда для удобства можно использовать псевдонимы для пространств имен.

namespace scl = ScL;

Развиваю библиотеку "умных" оберток https://habr.com/ru/post/650701/ и для меня это нововведение весьма полезно. Сейчас код представляет собой большую портянку макросов (например, здесь https://gitlab.com/ssoft-scl/module/feature/-/blob/main/src/Detail/Operator.h), от которых теперь можно легко избавиться.
Не думаю, что такой стиль будет распространён в прикладных программах, а вот инструментальные библиотеки типа std, boost и др. обязательно это будут использовать.

Если "потокобезопасность" это добавление на каждую операцию лока, то это не потокобезопасность

Средства библиотеки позволяют сделать операцию lock, как на каждую операцию отдельно, так и на любой блок кода, в зависимости от желаний разработчика. На постоянной основе сейчас можно сделать блокировку так

using Map = Wrapper< map, ThreadSafe::RecursiveMutex >;
Map map;

void foo ()
{
    // в месте, где нужен постоянный lock
    auto map_ptr = &map;
        // map_ptr - умный указатель, который выполнил lock
    if ( map_ptr->empty() )
        map_ptr->doAnything();
}

Не откладывая в долгий ящик, всё в проекте поправил. Спасибо за замечание, действительно глаз режет.

Кодстайл ужасный, читать очень тяжело.

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

for ( const auto & value : values )
        cout << value << endl;

Как раз над этим сейчас и работаю).

Подход strong typedef к простой декларации новых типов на основе существующих известен достаточно давно и имеет свою реализацию, например, в Boost.
Здесь показана возможность реализации такого подхода с помощью "умной" обертки на основе примера из доклада на коференции CppCon 2018.

И FileName и Url являются отдельными типами, а не синонимами. В этом весь смысл.

Information

Rating
7,244-th
Registered
Activity

Specialization

Specialist
Lead
From 5,000 €