Организация подсистем в С++

в 12:12, , рубрики: c++, c++11, Песочница, Программирование, программирование как искусство, С++, метки: , ,

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

Пример

Необходимо написать программу, которая захватывает изображение с веб-камеры обрабатывает его и отображает результат на экран в реальном времени. После декомпозиции выявлено 3 сущности:

  • Подсистема захвата видеопотока.
  • Подсистема отображения.
  • Подсистемы обработки видеопотока.
class processor
{
public:
    static processor & singleton();
    void * process(void *data);

private:
    processor() {};
    processor(const processor&) {};
};
class capturer
{
public:
    capturer(const std::string &file_name);
    void * frame();
};
class render
{
public:
    render(const std::string &name, int width, int height);
    void start();
    void stop();
    void render_frame(void *frame); 
};

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

  • Читаемость кода (до 90% времени программисты тратят на чтение чужого кода).
  • Скорость работы программы (способ организации не должен накладывать дополнительные расходы).
  • Гибкость (возможность добавлять различные подсистемы, независимо от их интерфейса).
  • Простое и явное управление временем жизни объектов подсистем.

Способы решения

За время своей работы профессиональным программистом я успел поработать в нескольких компаниях, известных и неизвестных. Далее я приведу способы решения описываемой в данной статье проблемы, которые встречал на практике.

Синглтоны

Работа со всеми подсистемами в программе осуществляется при помощи синглтонов. Достаточно хорошее решение, но имеет один большой недостаток – это управление временем жизни объектов. И если с созданием синглтонов еще можно сделать воркэраунд, вызывая их явно в нужно порядке, то с удалением всегда появляются проблемы. Нередко проблема удаления объектов-синглтонов решается при помощи черной магии вроде синглтона типа феникс.

Божественный объект

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

Сигналы

Каждая подсистема реализует некий механизм обработки событий/сигналов из общей для всего приложения очереди. Идеологически очень хорошее решение, но на практике приводит к необходимости писать много ненужного кода, реализации ненужных интерфейсов и т.д. К тому же немного страдает скорость выполнения программы.

Мой способ организация подсистем

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

Выделяется 3 типа подсистем:

  • Управляемые(managed). Созданием и удалением этих подсистем мы занимаемся сами. К тому же они обязаны реализовывать некоторый заданный интерфейс, например, методы start() и stop(). Так же эти подсистемы имеют доступ к другим подсистемам в программе.
  • Неуправляемые(unmanaged). Создаем и удаляем их мы сами, но не накладываем никаких ограничений на интерфейс.
  • Внешние(external). Не управляем созданием и удалением. Нет ограничений на интерфейс.

Обращение ко всем подсистемам универсально и осуществляется через менеджер подсистем(master).

Код

Интерфейс управлемых подсистем

//  subsystem.hpp

class master_t;

class subsystem_t
{
    friend class master_t;

public:
    virtual void start() {}
    virtual void stop() {}
    
    virtual inline ~subsystem_t() = 0;
    
    inline master_t & master();
    
private:
    master_t *m_master;
};

// Implementation

inline subsystem_t::~subsystem_t()
{
}

inline master_t & subsystem_t::master()
{
    return *m_master;
}
Менеджер подсистем

//  master.hpp

# include "subsystem.hpp"
# include "noncopyable.hpp"

# include <vector>
# include <cassert>

class master_t : private noncopyable_t
{
public:
    template <typename SubsystemType, typename... Args>
    inline void add_managed_subsystem(Args ...args);

    template <typename SubsystemType, typename... Args>
    inline void add_unmanaged_subsystem(Args ...args);

    template <typename SubsystemType>
    inline void add_external_subsystem(SubsystemType *raw_pointer);
    
    template <typename SubsystemType>
    inline SubsystemType & subsystem();
    
    inline void start();
    inline void stop();
    
    inline ~master_t();
    
private:
    std::vector<subsystem_t *> m_subsystems;
};

// Implementation

inline void master_t::start()
{
    for (auto &subsystem : m_subsystems)
    {
        subsystem->start();
    }
}

inline void master_t::stop()
{
    for (auto &subsystem : m_subsystems)
    {
        subsystem->stop();
    }
}

inline master_t::~master_t()
{
    for (auto &subsystem : m_subsystems)
    {
        delete subsystem;
    }
}

namespace internal
{
    template <typename SubsystemType>
    inline SubsystemType ** subsystem_instance()
    {
        static SubsystemType *instance = 0;
        return &instance;
    }

    template <typename SubsystemType>
    struct unmanaged_holder_t : public subsystem_t
    {
        template <typename... Args>
        inline unmanaged_holder_t(Args ...args) : holder(args...) {}
        SubsystemType holder;
    };
}

template <typename SubsystemType, typename... Args>
inline void master_t::add_managed_subsystem(Args ...args)
{
    SubsystemType **instance = internal::subsystem_instance<SubsystemType>();
    assert(*instance == 0);

    *instance = new SubsystemType(args...);
    static_cast<subsystem_t *>(*instance)->m_master = this;
    m_subsystems.push_back(*instance);
}

template <typename SubsystemType, typename... Args>
inline void master_t::add_unmanaged_subsystem(Args ...args)
{
    SubsystemType **instance = internal::subsystem_instance<SubsystemType>();
    assert(*instance == 0);

    internal::unmanaged_holder_t<SubsystemType> *unmanaged_holder = new internal::unmanaged_holder_t<SubsystemType>(args...);
    *instance = &(unmanaged_holder->holder);
    m_subsystems.push_back(unmanaged_holder);
}

template <typename SubsystemType>
inline void master_t::add_external_subsystem(SubsystemType *raw_pointer)
{
    SubsystemType **instance = internal::subsystem_instance<SubsystemType>();
    assert(*instance == 0);

    *instance = raw_pointer;
}

template <typename SubsystemType>
inline SubsystemType & master_t::subsystem()
{
    SubsystemType **instance = internal::subsystem_instance<SubsystemType>();
    assert(*instance != 0);
    
    return **instance;
}
Пример использования

//  main.cpp

#include "subsystem.hpp"
#include "master.hpp"

#include <iostream>
#include <string>

// external
class processor
{
public:
    static processor & singleton()
    {
        static processor instance;
        return instance;
    }

    void * process(void *data)
    {
        std::cout << "processor: process" << std::endl;
        return data;
    }

private:
    processor() {};
    processor(const processor&) {};
};

// unmanaged
class capturer
{
public:
    
    capturer(const std::string &file_name)
        : m_file_name(file_name)
    {}
    
    void * frame()
    {
        return nullptr;
    }
    
private:
    const std::string m_file_name;
};

// managed
class render : public subsystem_t
{
public:
    render(const std::string &name, int width, int height)
        : m_name(name)
        , m_width(width)
        , m_height(height)
    {
    }
    
    virtual void start() 
    {
        std::cout << "render start" << std::endl;
    }
    
    virtual void stop() 
    {
        std::cout << "render stop" << std::endl;
    }

    
    void render_frame(void *frame)
    {
        void *processed = master().subsystem<processor>().process(frame);
        
        std::cout << "Render[" << m_name << "]: render_frame " << processed << std::endl;
    }
    
private:
    const std::string m_name;
    int m_width;
    int m_height;
};


int main()
{
    master_t master;
    
    master.add_unmanaged_subsystem<capturer>("test1.avi");
    master.add_managed_subsystem<render>("wnd1:", 640, 480);
    master.add_external_subsystem<processor>(&processor::singleton());
    
    master.start();
    
    //while (true)
    void *frame = master.subsystem<capturer>().frame();
    master.subsystem<render>().render_frame(frame);
    // }
    
    master.stop();
    
    return 0;
}

Автор: Inkooboo

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js