We've been doing this for 50 years, you don't need a class hierarchy for your event struct.
enum EventType {
EVENT_NONE,
EVENT_TEMPERATURE_READING,
EVENT_SMOKE_DETECTED,
EVENT_CARBON_MONOXIDE_DETECTED,
};
struct Event {
EventType type;
union {
TemperatureDeadingData temperature_reading;
SmokeDetectedData smoke_detected;
CarbonMonoxideDetected carbon_monoxide_detected
} data;
};
std::vector<Event> events;> We've been doing this for 50 years
Code that turns into an unmaintainable mess the moment even one of the types involved contains dynamically allocated data? Yes, seen that thousands of times.
The article creates a type that has typesafety, resource management and basic copy/assignment operations backed in.
Yes, though in modern C++, don't do this, use std::variant instead. It has better type-safety (nothing stops you from writing to one member of the union but reading from another, causing UB) and handles lifetimes much better. Your version leaks, because struct Event wont destruct any of the nested types inside the union, and in the example, they have heap-allocated data (std::strings and the like). In order to that properly with a tagged union, you need to define the destructor of Event yourself (which is not done in your example) and call the correct destructor manually of the active union member.
Tagged unions (especially with resource-managing types) is hard to implement correctly in C++ (and not trivial in C either). Don't do it, use std::variant.
That doesn't work if you are writing a library where users of the library are able to implement their own event types.
Even for a self-contained code base, it's a pain compared to a type that just works with any current or future derived class.
What you wrote there is just an unsafe way to do std::variant. This has nothing to do with what the blog post is about.
The blog is about Open polymorphism. Where the list of compatible Object can be extended dynamically (Which is handy for some type of error report for instance).
You can do that since forever in C++ (Since the first support for inheritance), the only thing std::polymorphism brings is the ability to do it without dynamic allocations, emplacement new or lifetime problems.
When this piece of code exists in a library, it makes it pretty much impossible to extend by the user of the library.
Typical C approach. In C++ this should be done with std::variant instead. It has the exact same binary layout, but is type-safe (compared to union usage).
Yes, the article covers this (mentions the std::variant). This makes it a closed set though. Other differences is that your Event here is as big your biggest event type, and it doesn't go through normal inheritance mechanisms. With polymorphic you could just have an abstract Event and implement your subclasses normally.
A way to see it is some sort of type-erasure over destruction and copy-construction (plus another corner case for mismatched allocators).
The constructor of polymorphics captures the type of the subclass during its constructor (they didn't make the same mistake as with shared_ptr of giving it a pointer constructor, like the UB in case the destructor is not virtual in Base *b; shared_ptr<Base> sp{b}). With this, it can instantiate a template (a control block) where the subclass is known.