The callback interface has been suggested in a few of the other answers, but it has drawbacks:
- Many (potentially significantly) different classes relying on the same interface. These classes different needs may corrupt the clarity of the interface, what started out as PlaySound( sound_name ) becomes PlaySound( string sound_name, bool reverb, float max_level, vector direction, bool looping, ... ) with a bunch of other methods (StopSound, RestartSound, etc etc)
- Changes to the audio interface will rebuild everything that knows about the audio interface (I find this does matter with C++)
- The provided interface only works for the audio system (well, it should only be for the audio system). What about the video system, and the networking system?
One alternative that has also been mentioned is to make the audio system calls static (or the audio system interface a singleton). This will keep dog construction simple (creating a dog no longer requires knowledge of the audio system), but doesn't address any of the issues above.
My prefered solution is delegates. The dog defines its generic output interface (IE Bark( t_barkData const& data); Growl( t_growlData const& data ) ) and other classes subscribe to this interface. Delegate systems can become quite complex, but when properly implemented they are no more difficult to debug than a callback interface, reduce recompile times, and improve readability.
An important note is that the dog's output interface does not need to be a separate class that the dog is provided with at construction. Instead pointers to the dogs member functions can be cached and executed when the dog decides it wants to bark (or the shape decides to draw).
A great generic implementation is QT's signals and slots, but implementing something so powerful yourself will prove difficult. If you would like a simple example of something like this in c++ I would consider posting one but if you're not interested I'm not going to take the time out of my Saturday :)
Some drawbacks to delegates (off the top of my head):
1. Call overhead, for things that happen thousands of time a second (IE "draw" operations in a rendering engine) this has to be taken into account. Most implementations are slower than virtual functions. This overhead is utterly insignificant for operations which do not happen extremely frequently.
2. Code generation, mostly the fault of C++'s limited pointer-to-member-function support. Templates are practically a requirement to implement an easy to read portable delegate system.