Question

I'm doing the analysis for a software which I've had in mind for a long time. Its purpose is to turn sound into an image, apply graphical transformations on it, and turn it back into sound to hear the result. I plan to write it in C++ and I want to make it as modular as possible. As I am relatively inexperienced in modular design, my approach to the problem may be naive.

The app would be an assembly of several Modules which have DataInputs and DataOutputs that could be connected to each other. Basically, when a module has some data to send, it can tell one of its outputs to send a DataBlock to all of the inputs it is connected to, and these inputs would store the data block until their own module tells them to read it for further processing. This yields the following class diagram (excerpt):

class diagram summing up everything

The part I'm really not sure about is which type the inner data (DataBlock::data) should take. Up to now I've put void* as a type because it would allow me to statically cast it back into anything I want it to be without much trouble, but for many reasons which I'm not going to enumerate here, this is obviously not a solution I want to go with. A solution I've also come up with is to make an abstract BaseData type which any concrete data type would inherit from, like so:

alternative solution

I think this would be an okay solution, but I'm concerned about using inheritance "just" to make it so concrete data types have something in common while they represent completely different stuff. With this solution, modules would have to handle data blocks differently based on their type (given by the DataBlock::dataType field), which I think is fine if done correctly.

What is this design worth? Am I headed in the right direction or am I going to suffer the pain of a thousand fell gods because of some flaw I overlooked? What are potential alternatives and/or well-known design patterns for that purpose?

Was it helpful?

Solution

A base class makes sense, if and only if, you can easily imagine performing operations on things of the type of the base class. In your example above (to the degree to which its fleshed out) - it appears this is not the case.

If I were designing something like the above, I would have each module define whatever API made sense for it, and feel no need to make them have inputs and outputs which could be manipulated generically. I think this is the crux of the problem you've artificially created for yourself.

If - however - you do have good reason to want to treat these three kinds of objects generically, there are two families of good ways to go about that in C++.

  • templates
  • variant union (tagged union)

TEMPLATES

You can write algorithms (procedures) which operate on an arbitrary 'typename T', and produce whatever types make sense for output.

VARIANT UNION To use a variant union (see https://en.wikipedia.org/wiki/Tagged_union) there are a number of libraries or language features you can use. However, problably the least weak approach would be

- https://en.cppreference.com/w/cpp/utility/variant
  I'm not too fond of this approach, as its syntactically quite ugly.
Licensed under: CC-BY-SA with attribution
scroll top