Why is C++ perceived as difficult to learn?
The beginnings of C++ saw it as an extension to C, only using the new paradigm, object-oriented programming (OOP), thus promising to solve the many problems of growing code bases. This initial version of C++ is unforgiving; you, the programmer, had to deeply understand how memory allocation and release works and how pointer arithmetic works, as well as guard against a myriad of subtleties that you’d be likely to miss and that usually ended up in an unhelpful error message. It didn’t help that the prevalent cultural zeitgeist of programmers back then was that a real programmer had to know all the intricacies of CPUs, RAM, various assembly languages, OS workings, and compilers. It also didn’t help that the standardization committee did almost nothing to reduce the possibility of such errors for decades. No wonder the fame of the language is following it almost 40 years later. My experience learning it only helps to understand the struggles to learn the language back then.
I had my first touches with C++ during my polytechnics studies, in the 90s. They had left me both intrigued and puzzled. I understood the power of the language, while it was actively fighting against me – or that’s how I perceived it. I had to struggle to write code that worked. I was not yet familiar with STL, which was yet to gain notoriety as part of the standard, so most of my first C++ programs dealt with pointer usage. A common question at C++ exams was about differentiating between an array of pointers and a pointer to an array. I can only imagine how helpful the complexities of the language were for building exam questions!
For the record, see here the difference between pointer to array and array of pointers, a common exam question for C++:
int(*pointerToArrayOf10Numbers)[10];
int *arrayOfTenPointers[10]
I continued learning C++ through practice and from books I could find before the internet would make the knowledge available to everyone. But the biggest jump in my understanding of the language was a project I worked on around the 2000s. The project lead, a very technical Belgian man, set for us very clear guidelines and a process we had to follow to get the best C++ code possible. This need for excellence did not come simply from his desires but from the project needs: we were building a NoSQL database engine many years before they would be given this label.
For this project, I had to study and know all the rules from the two seminal books on C++: Effective C++ and More Effective C++ by Scott Meyers. The two books document in total 90 guidelines for C++ programmers, ranging from issues of resource initialization and release to minute ways to improve performance, inheritance, exception handling, and so on. This is also when I started using STL extensively, although the standard library was much more limited in scope than it is today.
This newly acquired knowledge made my C++ programs more reliable and made me more productive. An important contributing factor was the process we used in synergy with the wisdom of the two books. We wrote unit tests, we performed design and code reviews, and we carefully crafted our code knowing that it would be dissected by a colleague before getting accepted in the code base. This made our code quasi-bug-free and helped us implement complex features with high performance in a reasonable time.
However, the language was still fighting against us. We knew how to write good C++ code, only it required a level of attention and care that inevitably slowed us down. Mastering C++ was not enough; the language had to give something back.
After this project, I left the C++ world and learned C# and managed C++, Java, PHP, Python, Haskell, JavaScript, and Groovy, to limit myself to those languages I’d used for professional programming. While every programming language offered higher abstractions and fewer headaches compared to C++, I still had nostalgia for my formative years in programming. The fact that I knew C++ and all the intricacies of memory management gave me a deep understanding of the inner workings of these other languages, allowing me to use them to their fullest. Haskell proved to be very familiar to me since it was closely mapping the meta-programming techniques I’d learned from the seminal book by Andrei Alexandrescu, Modern C++ Design. C++ was living on in my mind, not only as the first programming language I used professionally but also as a foundation for every other language I’ve used since.
To my delight, around 2010, the news came that the C++ standardization committee was finally making bold and frequent changes to the language. The last C++ standard had been for many years C++ 98; suddenly we were seeing a new version every three years. This rolling release of new versions of the standard allowed the introduction of the functional programming paradigm, of ranges, of new primitives for parallel and asynchronous programming, of move semantics. But the biggest change for anyone who wants to learn C++ today is the simplification of memory management and the introduction of auto
types. The big breakthrough offered by these changes is that a Java or C# programmer can understand modern C++ programs, something we weren’t sure about back when Java and C# started.
This means the language is much easier to learn today than in the 90s. A good example of this change is the complete irrelevance of the old exam question on the difference between an array to pointers or a pointer to arrays; naked arrays can easily be replaced with a vector<>
or a list<>
, while pointers are replaced with the more precise shared_pointer<>
or unique_pointer<>
. This in turn reduces concerns related to allocation and release of memory for the pointers, thus both cleaning up the code and reducing the potential for the inscrutable error messages so prevalent in C++ 98.
We can’t say, however, that the C++ language is as easy to learn as the other mainstream ones today. Let’s see why.