2

I tried searching within questions dedicated to design patterns/data exchange/classes design, to no avail. I am specifically programming in c++, but being this mainly a design problem, I think is quite a general one.

What I am trying to do is designing the data exchange between at least two classes, could be more, as follows:

  • One class reads images from disk and shares them
  • Arbitrary number of classes (0+) read and process these images independently

The sharing class should not be constrained to the presence of consumer classes. Not being an expert, only options I could think of are either publish-subscribe machinery or using a shared memory.

What are the possible solutions for such a problem, and their pros and cons?

Thank you in advance

Tambo
  • 93
  • 4

2 Answers2

1

You could implement it as a classic producer-consumer pattern. You did not mention whether producers can work from different threads, but I will assume multi-threading capability to make this solution more flexible.

// 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);
  }
};

This a very, very beta-version concept, but it should give you an overview. Some notes:

  • There should be, of course, a single queue instance. It could be instantiated independently and passed to both classes as a constructor argument (via pointer or reference), but it also could be a member of ImageProducer class which could provide a public accessor to obtain the pointer/reference to it - the choice depends on particular needs.
  • Currently, the logic does not include clear point when processing should end. Queue could have an additional bool flag (e.g. m_processingActive, possibly wrapped in std::atomic<>). This flag would be initialized to true during construction and, after last image is produced, changed to false by the producer. Consumers would end waiting for images when queue becomes inactive.

There are probably some additional improvements, some things may be done differently and, possibly, better. But this basic concept is a quite good starting point (I hope).


Of course, you are not limited to a single ImageConsumer class. Actual processing function (processImage in my code) could be a virtual function, which is implemented in specialized classes.

Mateusz Grzejek
  • 11,698
  • 3
  • 32
  • 49
0

Publish-subscribe is a very generic design pattern. One way to implement it is using shared memory, but doesn't work very well over a network.

I'm going to assume that you want to do this in-process, multi-threaded.

Since the data seems to be fairly large, but static you should not put the images in the sharing mechanism, but rather allocate it and pass around pointer to the allocated memory. You can clean it up at the end or if you're not sure when the consumers are done with it, use a std::shared_ptr. You can also pass around the actual image data but that would cause it to be copied multiple times. Might be ok if it's small.

Now to implement the published-subscriber mechanism in a thread-safe way is a bit hard and easy to mess up. I would suggest using a library (boost::signals2 seems recommended).

Sorin
  • 11,863
  • 22
  • 26