Skip to main content

Learning to Learn: C++

·2514 words·12 mins

C++ is a huge language. It has tools form imperative, functional, object-oriented, and generic paradigms. And that leaves out the extremely fine control over things like memory allocation strategies in the standard library not generally available elsewhere. In this post, I present my learning path through C++ and offer some suggestions for learning this multi-faceted language.

Order of Topics #

This is not intended to be an exhaustive (for that would be far too long), or optimal (for that would be context dependent) listing of the topics, but rather the path that I took through the language.

Beginning C++ #

Everyone needs to start somewhere, for C++ I would start here.

For this section, I would read at least Effective C++ and Effective Modern C++, and then I would skim the C++ Core Guidelines. Together these will give you a broad basis to learn more about C++. The other references are still useful, but maybe not as pressing.

  1. Effective C++ by Scott Meyers - still the best beginners book for C++ and overviews common design concerns in C++. While it focuses on C++98, much of this book still applies.
  2. Effective STL C++ by Scott Meyers - Overview the standard template libary (set of containers and algorithms included in the standard libary) and how to use the common parts effectively.
  3. More Effective C++ by Scott Meyers - Extensions to “Effective C++” but more special purpose than the base book. While it focuses on C++98, much of this book still applies.
  4. Modern Effective C++ by Scott Meyers - How to effectively use new features in C++11 which radically modernized the language.
  5. C++ Core Guidelines - How to effectively use newer parts of the language.

Here are some challenges to improve your C++ knowledge

  • Implement std::unique_ptr and then std::shared_ptr from scratch.
  • Implement std::tuple, std::variant and std::any from scratch.
  • Implement a function fmap that takes a arbitrary Container of with elements of type T, a function which may convert each T to a possibly different type V and then stores the result into an instance of that container. Ensure your function works for std::vector, std::map, std::optional, and std::tuple.
  • Implement a function curry that take a function of arity k and an argument of the type of the first argument and returns a function of arity k-1.
  • Implement a function flatten that takes possibly arbitrarily nested sequence containers and returns all of the items fully un-nested.

Standard Library #

It almost goes without saying that the C++ Standard library is incredibly useful, and you should almost always start here. Its not as complete as say Python’s standard library, but its far more flexible. I would use cppreference.com or devdoc.io to read documentation on the standard library.

I would at least know about the following core objects in the standard library ordered by how roughly important I find them.

NameUse
<algoritm>generally useful functions
std::unique_ptrpointers that are the only reference to an object in memory
std::shared_ptrpointers that automatically count references
std::formata typesafe printf-like alterative to iostreams
std::arraya statically allocated array with handy bindings
std::vectora dynamically resizing array
std::mapkey value store, often implemented as a red-black tree
std::unordered_mapkey value store, often implemented as a hash table
std::setkey store, often implemented as a red-black tree
std::unordered_setkey store, often implemented as a hash table
Iterator Concepts/Rangessimplifies accessing members of a collection
std::stringdynamically resizing character data
std::spana nonowning view into an existing container
std::string_viewa non-owning reference to character data
std::tuplea generic version of a struct useful for generic programming
std::lista doublely linked list
std::ostreamoutput to file, string, or stdout
std::istreaminput from file, string, stdin
std::exceptionindicates extra-ordinary circumstances

Build Systems and the C++ Ecosystem #

Tools are important to getting actual work done while programming. Because of the nature of C++ I find that it has and need more types of tools than you may have used in other languages. I target this section to Linux/Unix platforms because it is what I use most often. When I list multiple tools, you generally only need one, and I list them in order of personal preference.

For this section, read what each class of tool does; then use it as a reference when you need an instance of that kind of tool. By no means is this a comprehensive list.

PurposeToolWhy
Backend BuilderNinjaMuch faster than Make
Backend BuilderMakeIncredibly common on Unix platforms
Backend BuilderBearGenerates compile-commands.json files for other projects
Build SystemMesonEasy to use, Very fast
Build SystemCMakeMore easy to use
Build SystemAutotoolsWorks on esoteric platforms where other choke
Compile CacheccacheDramatically speeds up incremental builds
Distributed Buildsicecc/icecreamSpread builds out to a server of faster machines
Code Formattingclang-formatEasy, Highly customizeable, sane defaults
CompilersclangBetter Error messages, clang/llvm Ecosystem
CompilersGCC g++Currently still faster, more common
IDE IntegratinclangdClang based IDE integration shows warnings and advice
DebuggerlldbHighly programmable, handles templates well, easy to use
DebuggergdbThe standard debugger, esoteric interface, more on gdb here
DebuggertemplightSpecialized debugger for compile time C++ code
DebuggermetashellAn older, ease of use tool built atop templight
IndexingExhuberant CtagsThe de facto tool for this job
Lintingclang-tidyMost extensive and “correct” linter
ProfilingperfLinux Specific, Extremely robust, easy to use
Searchingag/ripgrepinsanely fast, sane defaults, not syntactic sensitive
Searchingclang-querySlow, semantic sensitive
SearchinggrepCommon, pretty fast, not semantic sensitive
TracerltraceTrace shared library calls
TracerstraceTrace system calls
TracerdtraceTrace/Profile kernel and elsewhere, not widely available
Tracerllvm-xrayTracer with similar design principles to dtrace, but requires recompilation and is more available
Leak CheckingLeak Sanitizerlocate various memory leaks in programs
Undefined Behavior CheckingAddress Sanitizerlocate various memory misuses in programs
Undefined Behavior CheckingMemory Sanitizerlocate uninitialized reads in programs
Undefined Behavior CheckingUndefinedBehavior Sanitizerlocate other undefined behavior in programs
Undefined Behavior CheckingThread Sanitizerlocate data races in programs
Package ManagerSpackPackage manager focused on HPC; Similar to PIP
Package ManagerVcPkg/Connan/WraptoolC++ centric package managers; you miliage may very depending your usecase

I’ve recently added a learning to learn document for CMake

You’ll also eventually decide that you need/want some libraries to get useful work done. This post would be remiss if I didn’t mention a few cross cutting libraries.

  • Boost - a family of libraries that stretch the limits of what C++ can do. Libraries from Boost often become standardized. The best reference I have found is The Boost C++ libraries book.
  • Qt - While primarily a UI toolkit, it features a bunch of useful features. The online documentation is great.
  • Abseil - Google’s take on a general purpose library. Provides backward compatibility for new standard concepts. The comprehensive docs are sparse, but the code is well documented.

Here is a list of other libraries that I have used and would recommend.

PurposeLibraryWhy
Commandline parsinggetoptVery common command line parser, very portable, not pretty
Unit TestingGoogle TestVery common testing tool, easy to use
BenchmarkingGoogle BenchmarkVery common benchmarking tool, easy to use
NetworkingProtobuf/gRPCNetworking Server/Communication framework
NetworkingBoost.ASIODe facto C++ native networking library
Networkingsys/socket.hStill works, arguably simpler than ASIO
GraphsBoost.GraphTons of standard graph algorithms and structures
Date Mathdate.hIncredibly fast, easy to use; now in the standard library
JSON/XML ParsingBoost.PropertyTreeEasy to use
JSON Parsingnholman-jsonExtreemly easy to use
Distributed ProgrammingHPXA different take on HPC; I would argue easier to use
Distributed ProgrammingBoost.MPIMore native than standard MPI; fewer interfaces
Distributed ProgrammingOpenMPI/MPICHVery flexible
  • Profile some C++ that you wrote. What is are the bottlenecks in your code and why?
  • Port the build system of a package that you use to another build system. What was easier or harder?
  • Configure your build system to run your tests, run clang-format, clang-tidy and clangd
  • Create a program trace with ltrace, strace, llvm-xray, and perf compare and contrast the outputs
  • Write a clang-query script to find all references to a function or of an enum value.
  • Try accelerating your build with ccache/sccache or icecream/distcc.

Profiling and Speeding up C++ builds #

C++ can take a long time to build especially when your code is really template heavy. First just adopt ccache, it will dramatically speed things up for you for incremental builds.

Here is how I profile C++ builds. First if you are using ninja, you can clean your build, delete your .ninja_log file, and run your build. This will populate this file with a log of the build process. Next, You can extract timings for the build using this ninja stats script written in AWK. Once you’ve narrowed down your search to a few files, you can use clang’s -ftime-trace to get a detailed view of what is taking so long to compile/instantiate. See The Blog post “time-trace: timeline / flame chart profiler for Clang” for more information about how to use these traces.

Once you’ve made as many changes to your problematic structures and functions as you can, you may further reduce build times by using “precompiled headers” and “unity builds”. Precompiled headers work by serializing the compilers internal state so that the parsing phase of C++ can be skipped. Unity builds reduce the time required to instantiate identical headers by grouping source files for compilation. These both are not without their drawbacks for tooling which relies on source file names such as clangd, but I am hopeful that both of these techniques will be obviated with C++20 modules while improving the tooling situation. However, at time of writing, C++20 modules are not yet completely implemented by most major compilers.

  • use clang’s -ftime-trace or a similar feature in another compiler to profile your build. What could you do to accelerate the build?

Object-Oriented C++ #

C++ has a uniquely complicated object oriented system. Most of this is due to the use of templates for generic programming and Turing-completeness of templates.

For this section, the ordering is less important. Several of these books apply to more than just C++, but are especially important in a language as verbose as C++.

  1. “Design Patterns Elements of Reusable Software” by the “Gang of Four” - before the pitch-forks come out: 1) the examples of patterns in this book are often in C++, 2) object-oriented programming is object-oriented programming, and it doesn’t change much from language to language. I also find that most people struggle in OO to reinvent the wheel labeled by these four in 1994. It changed the way that I structure programs that I write.
  2. “Refactoring: Improving the Design of Existing Software” by Martin Fowler - over the course of learning C++ you’ll discover that you’ve done horrible, terrible things. This book while targeted at a general audience will help you fix them. It also puts the proper focus on usable, understandable interfaces and code over the spaghetti I often see in new C++ developers.
  3. The Pimpl Idiom - Pointer to Implementation is a powerful technique to reduce compile time dependancies. Read the set of two articles entitled “Compilation Firewalls” by Herb Sutter.
  4. The RAII Pattern - Resource Acquisition is Initialization is a fundamental to memory and exception safe programming in C++. Read the article about RAII on CPP Reference.
  5. Modern C++ Design: Generic Programming and Design Patterns Applied by Andrei Alexandrescu - One of the few uniquely C++ Object-Oriented books I have read. It assumes a fair bit of knowledge on generics and templates so read about it first.
  6. Metaclasses: Thoughts on Generative C++ by Herb Sutter - This video highlights were object oriented C++ maybe going at time of writing in early 2018.
  • Wrap a c library that you use to use RAII. How much more concise is the user code?
  • Try implementing generic versions of the Gang of Four patterns from “Object Oriented Design Patterns”. The flyweight pattern and factory pattern are especially rewarding to implement.

Generic C++ and Templates #

Generics and Templates are among C++ most powerful features. Every C++ programmer should know the basics.

For this section, the ordering is especially important, the later items are quite advanced and build on the previous items.

  1. “C++ Templates the Complete Guide” by David Vandevoorde, Nicolai M. Josuttis, and Douglas Gregor - An easy as possible introduction to the sometimes black magic of C++ templates.
  2. Curiously Recurring Template Pattern - while discussed in the Vandevoorde book, I found the article, “Polymorphic Clones in Modern C++” by Jonathan Boccara a much more practical example of where this may be used.
  3. “Modern C++ Design: Generic Programming and Design Patterns Applied” by Andrei Alexandrescu - I listed this in the object-oriented section, but this book opened my eyes the possibilities of C++ templates and generic programming in general.

If you can’t figure out why a particular template won’t compile, consider using templight or metashell.

Functional C++ #

This is an evolving category of C++. As such, I expect these to be much more in the coming years.

For this section, I would read all of the articles, they are not too long.

  1. lambda expressions - a key building block for functional programming in C++. Read the CPP reference documentation on lambdas.
  2. std::function - a generic means of storing type-specified references to a function. Read CPP references documentation on std::function
  3. <algorithm> - a example of a library that makes extensive use of functional concepts. Read CPP references documentation on <algorithm>

What next? #

When you’ve read most of the above, and are still looking to improve, I use the following to stay sharp.

  1. CPPCon - the yearly C++ developers conference. Filled with thought provoking talks and most are on YouTube.
  2. Read llvm’s libtooling, clang’s libC++ or Google’s abseil source code to see clear examples of well-written C++ code.
  3. Read the C++ Standard - it’s not for the feint of heart. The final version is behind a pay-wall, but the drafts are not. Read this if you want to understand some of the deeper behaviors of C++.
  4. C++Weekly - A video podcast that overviews different aspects of C++ in bite size chunks.
  5. CPP Cast - An audio podcast that covers upcoming C++ news

Suggestions to Learn the language #

I have two key suggestions to learn the language:

  1. Start small: choose a subset of the language that you want to learn well. The language is too large for most if not all people to be an expert on all parts of the language. Somethings like parameter passing should be in everyone’s subset, but the oddities of std::atomic<>, vector<bool> or the CRTP probably don’t need to be.
  2. Expand your subset as needed: choose a series of small projects that motivate why you want to learn various aspects of the language. This will help you practice and remember what you’ve learned.

I hope you find this useful. Until next time!

Change Notes #

  • 2023 - improved formatting, updated tools
  • 2020 - Added section on build profiling, updated tools, library components to learn, and further resources
  • 2018 - Initial version
Author
Robert Underwood
Robert is an Assistant Computer Scientist in the Mathematics and Computer Science Division at Argonne National Laboratory focusing on data and I/O for large-scale scientific applications including AI for Science using techniques of lossy compression, and data management. He currently co-leads the AuroraGPT Data Team with Ian Foster. In addition to AI, Robert’s library LibPressio, allows users to experiment and adopt advanced compressors quickly, has over 200 average unique monthly downloads, is used in over 17 institutions worldwide, and he is also a contributor to the R&D100 winning SZ family of compressors and other compression libraries. He regularly mentors students and is the early career ambassador for Argonne to the Joint Laboratory for Extreme Scale Computing.