0

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?

  • What kinds of different types do you want to store in DataBlock? Why is it necessary anyway to store different types that will only be selected at runtime (so it can't be expresses with a simple template)? I have a vague feeling of YAGNI here. – leftaroundabout Jul 10 '18 at 13:33
  • As I said my approach may be naive. DataBlocks will store audio samples as well as pixel data, or even commands in some cases. I failed to mention that previously, sorry. IMHO, this could be useful for the audio playback module for example: it needs to receive audio samples to play as well as commands to know when to start and to stop. That's why I think I need different types of data blocks. However, they need to be all passed through the same kind of data pipelines, hence the inheritance from a common abstract type. Can you elaborate on how templates could be used in this case? – qreon Jul 10 '18 at 16:09
  • 1
    Well, if you have one pipeline that only ever transports audio, then it would most sensibly use DataInput<Audio>, which in turn has DataBlock<Audio> which is by the type system guaranteed to only store audio, not something else. — Commands are another thing, and I daresay you should probably handle them in a separate fashion entirely. – leftaroundabout Jul 10 '18 at 16:13
  • I haven't thought twice about the fact my pipelines would ever carry only one type of data. You should make it an answer so I can validate it. Thanks a lot! It's a bit off-topic but what would advise me to look into for commands (patterns, etc)? – qreon Jul 10 '18 at 16:23
1

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.
  • I'll look into templates more precisely, thanks for the hint. I did think about them but couldn't see how to use them, @leftaroundabout clarified it in the comments above. :) – qreon Jul 18 '18 at 16:57
  • I'm not sure templates are a good fit for what you've described, but they maybe at some point. Templates work well for an algorithm IF AND ONLY IF you can imagine writing a procedure DoSomething(T element) { ... do soemthing to each element which might be from different models; } This CAN happen (like sort() is a good example of something like that). – Lewis Pringle Jul 18 '18 at 17:28
  • If you are not used to using templates, a good way to start is to WAIT until you see you are writing duplicative code. When you see the same patterns over and over again, templates are a good way to capture that and reduce the duplication. But I think they maybe just a source of confusion in the early object oriented design phase. – Lewis Pringle Jul 18 '18 at 17:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.