Let’s start from a little fake story.
Alice is a young aspiring architect who was recently trusted with a new and very important project. Bob is well experienced architect who has been in the industry for decades. One day Alice comes to him and says:
“I’ve got the best idea ever! You know, C++ has functional capabilities, so in my new projects all the functions will be pure and all data structures immutable.”
And the Bob then replies: “Have you been smoking Lisp? Do you imagine an amount of runtime overhead? Sure, referential transparency is nice, but if you have to abandon mutable arrays for it, it’s not worth it.”
The other day Alice comes to Bob with another wild idea: “Have you heard about new static meta-programing features in C++? These are great! I will compute every little thing I can in compile time.”
The Bob replies: “Can you imagine the build time of this thing? Of course, if you can precompute something beforehand, then sure, why not. But why don’t you just take a simple look-up table instead?”
Soon enough Alice returns to Bob and says: “I’ve been cleaning my closet and suddenly found gang of four book. Now I think OOP is great, and I should encapsulate every piece of data in my project.”
“Ok”, says Bob, “that will do”.
I’m sure you have a couple of objections to Bob. Of course going all OOP in multi-paradigm language is not always rational. And of course paranoid encapsulation as every programming concept has its drawbacks.
But this is not entirely my point. My point is, Bob, while being an excellent architect with a pile of successful projects behind his back, is biased because of his successful past. He teaches Alice not the best way possible, but only the way that worked for him for years. And it is completely ok. We all do. We are all Bobs. Everyone who teaches from his own experience is Bob, and everyone who teaches from someone else’s is a fraud.
But Alice would not live in the past. The future belongs to her, and she needs to learn the whole arsenal, not only the old ways.
A hip note (like a footnote, but higher)
There was, and probably still is, a very successful C++ project where encapsulation was openly discouraged. I’m talking about PopCap Games Framework. At some point PopCap started its development program, so they opened their framework for everyone who was willing to enter casual games market. And it was the most malleable framework ever. The most innovative casual games for the moment was developed in it.
Because you could. It was easy to hack your way deep into the framework, and make the feature you wanted. Of course this compromises modularity, and of course this leads to a huge technical debt. But who cares? Usual development cycle for a casual game is about 6 month. Then you just throw away your code and start anew. It is unheard for enterprise with their decades lasting code bases, but for casual games development this is a valid strategy.
You know, they say C++ is like Swiss Army knife. With all that screwdrivers, and the corkscrew, and the saw, and what not. Well, it kind of is. Except it isn’t. It is like a multi-tool where every tool is a knife. There is hunting knife, diver’s knife, paper knife, leather knife, carving knife, butter knife, dozen more knifes of any sort, and a machete. Just in case things go rough.
I think this metaphor is more precise, because you wouldn’t pick a corkscrew instead of a saw. The choice of the tool from a Swiss Army knife is rather obvious. But this doesn’t work when all the tools do essentially the same job. Technically you can live your whole life, build an entire career, with only one knife. The best one. The one you personally got skillful with.
Although it is not the best idea to keep to only one paradigm whatever it might be. The variety is here for a reason. Some tools are just better for some jobs. There is more than one way to skin a cat, but the best one is with a custom made rubber grip stainless steel cat skinner 3000.
And the thing that takes the most expertise in C++ is exactly picking the right knife for the job. If would be the easiest thing ever if only the industry itself would be easy. Like when you have oysters for dinner, you pick an oyster knife — and voilà. But programming is nothing like that. It’s more like: “carve me a hedgehog out of rubber tire under the acid rain; oh and the deadline is last Friday, so if you could time travel there it would be great”.
So how do you learn to pick the right feature, the right paradigm, the right approach in C++? The most obvious way — the front door — would be to study C++ until you done. The problem here is Bob. You would have to learn from him, and while absolutely in good faith he only can teach you what he learned. And remember, we are all Bobs.
The other way—the back door — would be learning not C++, but other more specialized languages. It makes the perfect sense within the knife metaphor. You don’t learn “multi-tooling”, I’m not sure if it’s even a word, you learn cooking, cutting-out, carving, shoe making and can opening. You learn skills, not the tools. And you use tools to learns skills too.
So here’s my own short list of languages that help you master C++.
For the low level.
I think Assembler is only viewed as horribly incomprehensible because we only resort to it when things go wrong. It’s like when you hunt the bug and you view a disassembly of highly optimized code with no macroses, no variable names, mo meaningful labels and you think: what is this? This is not code, this is an alphabet soup! I’m not going to read this!
; copy the handler to clipboard
invoke MessageBox, 0, addr TextBuffer, addr sClipboard, MB_YESNO
invoke OpenClipboard, 0
invoke GlobalAlloc, GMEM_MOVEABLE or GMEM_DDESHARE, 32
mov ClipboardMemoryHandler, eax
invoke GlobalLock, ClipboardMemoryHandler
mov ClipboardMemoryAddress, eax
invoke lstrcpy, ClipboardMemoryAddress, addr SmallerBuffer
invoke GlobalUnlock, ClipboardMemoryHandler
invoke SetClipboardData, CF_TEXT, ClipboardMemoryHandler
I’ve highlighted all native “assemblish” things leaving macroses, functions and variables in gray. As you can see, macro-assembler, while being as low level as software can get, is not that alien to modern programming. It’s 90% API calls and macroses.
2. Plain old C
For structural programming.
This might be somewhat controversial, but I think you should learn C. Let it be your survival training: here’s you Bowie knife, here’s your jungle, see you again in 44 years.
Learning C has a downside though. C and C++ are completely different ideologically , bringing C programming patterns into C++ without a second thought is almost always a bad idea. But my point is, you want to learn other languages exactly to make your reasoning about feature applicability more precise. You should learn C if only to learn to avoid C features in C++.
For objective oriented programming.
I really should have suggested Smalltalk, but I never wrote a line in it.
You might have heard Alan Kay’s quote: “I made up the term ‘object-oriented’, and I can tell you I didn’t have C++ in mind”. In Objective-C objects are truly isolated from each other and they only communicate from the single interface, and this is much closer to Kay’s original vision. When you don’t have any way to touch other’s private parts, encapsulation only comes naturally. You can’t just grab bytes from the memory, you have to ask their owners for them.
Inheritance is just a path messages from one object to another are routed. And polymorphism is even more simpler — you just post the message, and you really don’t care what object reads your message, as long as it’s capable of reading it. These traits are just implications of messaging conception.
For template and functional programming.
Well, it’s quite obvious why Haskell is great for learning functional approach. But it also disciplines you in declarative programming. Classic template meta-programming in C++ is also declarative, although it slowly goes out of fashion because of really neat fresh
More over, Haskell is statically typed and has type classes, which are similar to concepts — the cutting-edge technology in C++ world. With Haskell you can master it sooner than it officially gets into the C++ standard!
The problem is, Haskell comes from completely different family of languages. So maybe it’s best to split this exercise into three: Ada for generics, Scheme for functional programming, and Prolog for declarative programming. I only suggest Haskell, because it has better resources and bigger community as for now. Learn You a Haskell For a Greater Good alone is enough to want to try it out.
P. S. Well, I am also Bob. I only can share the experience I had. If you know better languages to master C++ with, and I’m sure you do, please leave a comment below.