Hello, and welcome to C++ Weekly. I'm your host, Jason Turner. I'm available for code
reviews and on-site training. In this episode I'm going to talk about modern C++ design patterns,
and before I get into that I just want to point out that I am currently scheduled to give a
workshop at NDC TechTown in Kongsberg, Norway from September 18th through September 19th. If
you want to check that out you can see here that I am planning to give the Best Practices course
that I really enjoy giving. It's kind of like my C++ Best Practices book, but if you've already
read my book you're going to get even more out of this class (it's not that one is a replacement for
the other), and you would have the opportunity to ask me questions about the best practices,
why I chose things like I did, push back, and we can have a great time together. Now if you're just
interested in taking some class and you aren't particularly interested in my Best Practices
class check out these other seven that are offered. Interestingly Klaus is offering a class
on Modern C++ Design Patterns, which is the topic of today's episode. Now I'm also planning to give
a C++ Best Practices workshop at CppCon 2023. The dates are not official yet. I have not officially
been selected for that yet, but I am hoping to, so check out links in these video descriptions
for the upcoming workshops that I might be giving if you've ever been interested in that.
Okay, so as you are hopefully aware I have my C++ Weekly repository on GitHub,
and this is where I track all of the episode ideas that I am working on, and you can feel
free to come in and thumbs up and thumbs down and comment. When I first created this list (you can
see an awful lot of these were created June 22nd, 2022; I am recording this episode on April 5th,
2023) the consistently top ranked one that I have not yet gotten around to recording is a
design patterns in Modern C++ series. I've decided it is time for me to just go ahead
and record this. I spent a lot of effort looking into this. Now I've decided that this ultimately
is not going to be a series, it is going to be a single episode, and the reason for that is quite
simple - I am not an expert in design patterns. It has taken me an extraordinary amount of research
just to look into the very first pattern that I came across, which is the Observer pattern.
Now I have all of these notes in here on this issue that you can go in and look at. This is
kind of the signal and slots idea from Qt. There's all kinds of classic examples - they
largely either have memory issues or assume a reference counted language. A lot of the
examples that I found ignore the possibility of cyclic references, which definitely bothers me.
It's easy to make naive implementations that either have way too much overhead,
can lead to cyclic references or have dangling references. There's so many different ways that
we can look at this, and if you come in here and check out my plan for this particular pattern and
go and look at everyone else's comments you can find all this information on signals and slots,
and observer patterns and existing libraries that work with this particular design pattern. Again,
all this detail, tons of questions that I brought up and the questions that you have to ask like,
"Do you want a one-to-many, a one-to-one, a many-to-one, or a many-to-many relationship?"
Let's just go ahead and do a quick click on this. So let's look at... I'm just going to pick the
very first one I have up here - the SimpleSignal implementation. Now this is four years old already
at this point, but the idea is that you declare some sort of callback and this would be your
slot, if you will, if you're used to Qt, and your signal is some way of emitting this. So in
this idea it's a decoupling. The entire point is that the things that are connected to each
other don't know anything about each other at all, and this is a great design technique
for simplifying interdependencies between types - works really well.
So this is my slot, if you will, this is my callback, and I am saying, "I'm going
to create some sort of signaling object; I'm going to connect it to this static function,
and then whenever I call 'emit' it's going to call that function." Now generally speaking with this
kind of technique I can have multiple functions connected to this, and then when I call 'emit'
then it calls all of those functions in the order that they were registered. This idea of signals
and slots, this Observer pattern, this connection kind of thing is fundamental to how Qt works. It
uses their meta object compiler to do this, but in Qt here is your Q_OBJECT based implementation.
You tell it this is a slot and this is a signal, and then you can 'emit' that some sort of event
happened. Now if anyone is connected to that signal, then they will automatically receive
that event and you can do that with the 'connect'. So I'm going to connect object A's valueChanged
to object B's setValue, and note here in Qt we are getting a pointer to each of these objects,
and that can work because Q objects are smart and everything works like we want it to. So I
started doing my own investigation into this and I have created many of my own versions.
Now I decided, and this episode is almost certainly going to be longer than I actually
want it to be, that instead of covering a specific implementation of a specific design pattern and
doing a series about that, I am going to give you some ideas for things that you should consider
when you are doing design pattern-like things in C++, and you can come to all of my examples here
and see where the pros and cons are for each of them for your own particular application. Okay,
so the first question that I think you should ask is, "Are the relationships that you're trying to
establish between two objects or whatever known at compile time?" Now I'm not suggesting that
you do a close compile time coupling of the types - that's a bad plan. That's the kind
of thing that we're trying to avoid, but at least theoretically you might be able to do something...
So this is largely hypothetical because this doesn't work great
for anything that's mutable because constexpr objects must be const.
Okay, so as always, I just want to ask, like,
"Are the things known at compile time?" Then the next thing that I would suggest would be,
"Are the relationships dynamic or are they fixed?" So let's just say...
Okay, so if we have this kind of scenario where we might want to make
some connections and then later add something to the connection
so that we can dynamically reconnect things, like, do we want that,
or do we want to require all of these things be known at construction time?
So maybe we want to have something like this... So perhaps it's fixed. We know at
the time of construction that this connection has however many senders, how many receivers it has,
or observers and observables that it has depending on whatever language you want to use here, and if
all of this is known at the time of construction, or not - if it wants to be dynamic. So you want
to consider that, and then you want to consider whether or not the connections, or the objects,
can outlive each other. So if we have this example that we've already discussed, then we know that
sender's created first, receiver's created second, receiver2 is created third, and the connection is
created fourth, and then we can safely send this and we know the order of destruction is going to
be connection, receiver2, receiver and sender. (If you don't understand that then I strongly suggest
you check out one of my Object Lifetime Puzzler books which is linked in this video description.)
So that works perfectly fine, but if I was going to do something like this:
Okay, so I have the version that is dynamic,
and let's just say hypothetically we had code that looked kind of like this:
Okay, so we have a very big question to ask ourselves -
"Is that call going to be a crash or not because receiver2 has gone out of scope?"
Does our connection maintenance logic in any way know that this thing is outliving this thing and
the connection is, you know, gonna try to call an object whose lifetime has already ended because
we had some sort of connection to a reference to an object that was popped from the stack?
So if they are going to outlive each other in some interesting way then we might have to do
something like shared_ptr or reference counted objects like Q_OBJECTs or something like that.
So I kind of, in my opinion, look at these things in this specific order. I'm going to try to figure
out if there's some sort of relationship, and, again, this is specific to the sender-receiver,
signal-slot, observer-observable from this Observer pattern, but if I can make some
sort of connection at compile time between these two things then I know it's safe. It's
just not possible for me to make something that's going to go wrong, and that is great.
If the relationship is fixed like this and it's not possible for me to add a
later object that's created in a different scope, then I can, again, guarantee a certain
level of safety between these objects. Furthermore if I do know that I want to make this as dynamic
as possible, then I have to be smart about it in some way. So again I have all of these different
possibilities - shared pointer-based versions, versions that are not self-registering, versions
that are reference based; and I tried to actually use a specific example of pressure, humidity and
temperature sensors that are connected to each other and thinking about signals and slots from
the perspective of, like, an embedded device or something. So in this case I have an observer,
and it is able to automatically unregister itself in its destructor, which then makes it safe,
so it goes out of scope before the connection does. Then it can do something intelligent,
and in this particular case I have here some something that's a little bit more
generic where it is all templated based, and we can do something interesting here without
knowing all of the types at compile time, but these still all rely on some sort of observer
and observable interface. Then I have an option of using std::function, which is fully decoupled, and
then I have some ideas for zero overhead compile time registration that you can click through here.
This is a much simpler version because all of the types and relationships have to be known at
compile time. So I can create this with a set of observers - the make observers; I'm passing
in a bunch of objects and their types are known at compile time and it can do the super lightweight,
high performance thing if that's your goal. So this is where I am going to stop this episode,
and at this point this is going to be everything that I am going to say about design patterns in
this channel - not going to make a full series about it. I just want you to stop and think and
say, "If I have my classic design pattern and it assumes some sort of shared pointer thing,
or has reference counting, or has object lifetime issues, then consider your alternatives. Can you
make the thing correct by construction, so you know what order these things are
going to be created and destroyed in, or do you have to use the shared pointer idea,
or does it make sense to use some sort of standard library thing like function or tuple to create
a safer, relatively easy to use version of all this?" Then to wrap this up, what I want to do is
point you to all of these lovely books that other people have written that I have links to. We've
got Head First Design Patterns, the Classic Design Patterns book, Hands-On Design Patterns of C++,
Design Patterns in Modern C++20, Modern C++ Design, and as I already mentioned, the C++
Design Patterns classes that Klaus Iglberger teaches. So go out there, explore these topics on
your own and kind of try to think about C++ best practices while you're doing that. So thank you
for watching this episode of C++ Weekly. I hope that this was actually a useful episode and gives
you a lot to look at and consider next time you're designing your own code. Thanks for watching.