Can you elaborate on your third point? What would a class need to do to affect debugging info?
Regarding your fourth point, sometimes an architecture can be vastly simplified if the source of information can abstracted away. For example, invoking a callback from a TCP client, batch replay service, unit test, etc. Sometimes object oriented design gets in the way.
To your first point, I think RAII and architecture primarily address this. I'm not sure that I see callback implementation driving this. Although I have seen cancellable callbacks, allowing the receiver to safely cancel a callback when it goes away.
>> Can you elaborate on your third point? What would a class need to do to affect debugging info?
Common implementations are a function pointer + void* pair, which in most debuggers just show you two opaque addresses. Better to include a info block -- at least in debug builds -- with polymorphic type pointers that can actually deduce the type and show you all the fields of the receiver.
>> sometimes an architecture can be vastly simplified if the source of information can abstracted away.
"sometimes" is doing a lot of heavy lifting here. That's my whole point -- more often than not I see some type of homespun functor used in cases that are _not_ simplified, but actually complicated by the unnecessary "plumbing."
>> RAII and architecture primarily address this
If the receiver uses RAII to clean up the callback, then you've reintroduced the "type-intrusiveness" that functors are meant to avoid...?
C++ was so much cleaner in the 90s, when it was still essentially "C with classes," which is how I like to use the language. Modern standards have turned it into an ugly mess.
I also use C++ as "C with classes," however I will concede that many of the modern C++ additions, particularly around templating, are extremely convenient. If you haven't had a chance to use requires, concepts, "using" aliases, etc I'd recommend giving them a try. I don't reach for those tools often, but when I do, they're way nicer than whatever this article is demonstrating from 1994! Oh yeah, also lambdas, those are awesome.
Amen. The syntax just kept getting more and more complicated. I gave up in the late 1990s. Ironically for this post, I now prefer to write everything in Clojure. It seems like my own journey has paralleled Rich’s journey. Maybe that’s why I appreciate so many of the design choices in Clojure. It’s not perfect, but it’s really, really good.
Heard about this watching Casey Muratori's "The Big OOPs" talk [0]. Thought it couldn't be _that_ Hickey, but turns out it was!
[0] https://youtu.be/wo84LFzx5nI?si=SBv1UqgtKJ1BH3Cw&t=5159
Red flags for me when I see nonstandard functors in a c++ codebase (esp if the "glue" is in a setup function independent of the objects):
(i) Have they thought about the relative lifetimes of the sender and receiver?
(ii) Is the callback a "critical section" where certain side-effects have undefined behavior?
(iii) Does the functors store debugging info that .natvis can use?
(iv) Is it reeeeeeeally that bad to just implement an interface?
Can you elaborate on your third point? What would a class need to do to affect debugging info?
Regarding your fourth point, sometimes an architecture can be vastly simplified if the source of information can abstracted away. For example, invoking a callback from a TCP client, batch replay service, unit test, etc. Sometimes object oriented design gets in the way.
To your first point, I think RAII and architecture primarily address this. I'm not sure that I see callback implementation driving this. Although I have seen cancellable callbacks, allowing the receiver to safely cancel a callback when it goes away.
>> Can you elaborate on your third point? What would a class need to do to affect debugging info?
Common implementations are a function pointer + void* pair, which in most debuggers just show you two opaque addresses. Better to include a info block -- at least in debug builds -- with polymorphic type pointers that can actually deduce the type and show you all the fields of the receiver.
>> sometimes an architecture can be vastly simplified if the source of information can abstracted away.
"sometimes" is doing a lot of heavy lifting here. That's my whole point -- more often than not I see some type of homespun functor used in cases that are _not_ simplified, but actually complicated by the unnecessary "plumbing."
>> RAII and architecture primarily address this
If the receiver uses RAII to clean up the callback, then you've reintroduced the "type-intrusiveness" that functors are meant to avoid...?
C++ was so much cleaner in the 90s, when it was still essentially "C with classes," which is how I like to use the language. Modern standards have turned it into an ugly mess.
I also use C++ as "C with classes," however I will concede that many of the modern C++ additions, particularly around templating, are extremely convenient. If you haven't had a chance to use requires, concepts, "using" aliases, etc I'd recommend giving them a try. I don't reach for those tools often, but when I do, they're way nicer than whatever this article is demonstrating from 1994! Oh yeah, also lambdas, those are awesome.
>ugly mess
That may be the case, but there are plenty of examples of elegant implementations.
JUCE, for instance:
.. I think that's kind of clean and readable, but ymmv, I guess?A sentence from the article: "Given the extreme undesirability of any new language features I'd hardly propose bound-pointers now."
It shows that C++ was considered too complex already in the 90s.
Amen. The syntax just kept getting more and more complicated. I gave up in the late 1990s. Ironically for this post, I now prefer to write everything in Clojure. It seems like my own journey has paralleled Rich’s journey. Maybe that’s why I appreciate so many of the design choices in Clojure. It’s not perfect, but it’s really, really good.