In my program's user interface, I have the concept of "control point values" (a control point value
represents the current state of a control on the hardware my program controls), and (of course) the concept of "widgets" (a widget
is a GUI component that renders the current state of a control point to the monitor, for the user to see and/or manipulate).
Since it is a pretty elaborate system that it needs to control, we have
- lots of different types of
control point values
(floats, ints, strings, booleans, binary blobs, etc)
- lots of different types of
widget
(text displays, faders, meters, knobs, buttons, etc)
- lots of different ways that a given
widget
could choose to render a particular control point value
as text (upper case, lower case, more or fewer digits of precision, etc)
If we just did the obvious thing and wrote a new subclass every time we needed a new combination of the above, we'd end up with a geometric explosion of thousands of subclasses, and therefore a very large codebase that would be difficult to understand or maintain.
To avoid that, I separate out the knowledge of "how to translate a control point value
into human-readable text in some particular way" into its own separate immutable object that can be used by anyone to do that translation, e.g.
// My abstract interface
class IControlPointToTextObject
{
public:
virtual std::string getTextForControlPoint(const ControlPoint & cp) const = 0;
};
// An example implementation
class RenderFloatingPointValueAsPercentage : public IControlPointToTextObject
{
public:
RenderFloatingPointValueAsPercentage(int precision) : m_precision(precision)
{
// empty
}
virtual std::string getTextForControlPoint(const ControlPoint & cp) const = 0
{
// code to create and return a percentage with (m_precision) digits after the decimal point goes here....
}
private:
const int m_precision;
};
... so far, so good; now e.g. when I want a text widget to display a control point value as a percentage with 3 digits of after the decimal point, I can do it like this:
TextWidget * myTextWidget = new TextWidget;
myTextWidget->setTextRenderer(std::unique_ptr<IControlPointToTextObject>(new RenderFloatingPointValueAsPercentage(3)));
... and I get what I want. But my GUIs can get rather elaborate, and they might have a large number (thousands) of widgets, and with the above approach I would have to create a separate RenderFloatingPointValueAsPercentage object for each widget, even though most of the RenderFloatingPointValueAsPercentage objects will end up being identical to each other. That's kind of wasteful, so I change my widget
classes to accept a std::shared_ptr instead, and now I can do this:
std::shared_ptr<IControlPointToTextObject> threeDigitRenderer = std::make_shared<RenderFloatingPointValueAsPercentage>(3);
myWidget1->setTextRenderer(threeDigitRenderer);
myWidget2->setTextRenderer(threeDigitRenderer);
myWidget3->setTextRenderer(threeDigitRenderer);
[...]
No worries about object lifetimes, no dangling pointers, no memory leaks, no unnecessary creation of duplicate renderer objects. C'est bon :)
shared_ptr
would be the only acceptable solution. I personally can not came up with such a code example. Note that something like "there are gorrillion of real world programs (and even programming languages) built around shared ownership of everything" is not really an answer.