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

Я пытался искать в вопросах, посвященных шаблонам проектирования / обмену данными / дизайну классов, но безрезультатно.
Я специально занимаюсь программированием на c ++, но, поскольку это, в основном, проблема проектирования, я думаю, что это довольно общая проблема.

Я пытаюсь сделать дизайн обмен данными между как минимум двумя классами, может быть больше,
следующее:

  • Один класс читает изображения с диска и акции их
  • Произвольное количество классов (0+) считывает и обрабатывает эти изображения независимо

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

Каковы возможные решения такой проблемы, а также их плюсы и минусы?

заранее спасибо

2

Решение

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

// Not important what this actually is.
class Image
{ };

using ImagePtr = std::shared_ptr<Image>;

// Shared queue which stores currently available images and
// encapsulates synchronization details.
class ImageQueue
{
private:
std::queue<ImagePtr> m_images;
std::mutex m_mutex;
std::condition_variable m_cond;

public:
void PostImage(std::shared_ptr<Image> image)
{
// Lock the queue, push image, notify single thread.
std::unique_lock<std::mutex> lock(m_mutex);
m_images.push(image);
m_cond.notify_one();
}

ImagePtr WaitForImage()
{
// Lock the queue, wait if empty, fetch image and return it.
std::unique_lock<std::mutex> lock(m_mutex);
if (m_images.empty())
{
m_cond.wait(lock, [&m_images]() -> bool { return !m_images.empty(); });
}
assert (!m_images.empty());
auto nextImage = m_images.front();
m_images.pop();
return nextImage;
}
};

// Image producer class, loads images and posts them into the queue.
class ImageProducer
{
private:
ImageQueue* m_queue;

public:
void LoadImage(const char* file)
{
auto image = loadAndInitializeImageObject(file);
m_queue->PostImage(image);
}
};

// Image consumer class, fetches images and processes them.
class ImageConsumer
{
private:
ImageQueue* m_queue;

public:
void ProcessImage()
{
auto image = m_queue->WaitForImage();
processImage(image);
}
};

Это очень, очень бета-версия концепции, но она должна дать вам обзор. Некоторые заметки:

  • Конечно, должен быть один экземпляр очереди. Его можно создать независимо и передать обоим классам в качестве аргумента конструктора (через указатель или ссылку), но он также может быть членом ImageProducer класс, который может предоставить общедоступный метод доступа для получения указателя / ссылки на него — выбор зависит от конкретных потребностей.
  • В настоящее время логика не включает четкую точку, когда обработка должна закончиться. Очередь может иметь дополнительную bool флаг (например, m_processingActiveвозможно завернутый в std::atomic<>). Этот флаг будет инициализирован как true во время построения и, после создания последнего изображения, будет изменен на false производителем. Потребители прекратят ждать изображений, когда очередь станет неактивной.

Возможно, есть некоторые дополнительные улучшения, некоторые вещи могут быть сделаны по-другому и, возможно, лучше. Но эта базовая концепция — неплохая отправная точка (я надеюсь).


Конечно, вы не ограничены одним ImageConsumer учебный класс. Фактическая функция обработки (processImage в моем коде) может быть виртуальная функция, которая реализуется в специализированных классах.

1

Другие решения

Публикация-подписка — это очень общий шаблон дизайна. Один из способов реализовать это — использовать разделяемую память, но не очень хорошо работает в сети.

Я собираюсь предположить, что вы хотите сделать это в процессе, многопоточным.

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

Теперь реализовать механизм опубликованных подписчиков в поточно-ориентированном режиме довольно сложно и легко. Я бы предложил использовать библиотеку (кажется, что boost :: signal2 рекомендуемые).

0