Book Review "Effective Modern C++" by Scott Meyers

It’s easy to find lots of reviews, comments and other references saying Scott Meyers has done an absolutely amazing and valuable job. And overall I agree with almost every worthy review, but I would like to emphasize the benefit of the work. So let’s try to point out some interesting stuff, solely for the preparation of the future reader, and not to violate a license of the book.

Even though the topic of the book’s content is aimed at C++11/C++14, and the current timeline prepares us for C++23, the reading will be usefull if you:

  • Do not have sufficient experience of C/C++ or want to fill in some gaps in basic knowledge.
  • Develop a project that supports the old language standard.
  • Systematize existing knowledge. But if you’re an experienced developer, you might be wasting some time because the content is not unique nowadays.

Starting from the very beggining, the reader will come across many simple and comprehensive examples of types deduction, template types propagation, forward reference (universal reference) and so on. I just want to unobtrusively consider one of them:

// return compile time array size
template<typename T, std::size_t N>
constexpr std::size_t arraySize(const T(&)[N]) noexcept
{
    return N;
}

int data[] = {1,2,3};
std::size_t size = arraySize(data); // 3

// possible alternative
#define ARRAY_SIZE(arr_) sizeof(arr_)/sizeof(*arr_)

In this simple case, it will work fine. The generated asm instructions are exactly the same as when building with -O3, but the second approach has obvious disadvantages:

  • If the arr_ is not compile-time array but just a pointer, pointer_size/element_size expression will be created, because the sizeof operator is CT, while the Scott Meyers’ example won’t compile.
  • If you declare int data[] = {};, which is illegal by the standard but still supports at least GCC_v13.1/Clang_v15.0, the arraySize function will produce CT failure, while the macro doesn’t, moreover the dereferencing of *arr_ without underlying own allocated memory, it also means an undefined behavior by the standard.

Item 4 explains how to check the actual type deducing by the compiler. I won’t repeat the existing ideas within the topic, but I want to expand the number of abilities. Luckily there is more options nowadays thanks to the stackoverflow discussion:

  1. cppinsights. It produces absolutely human-readable output after the INSIGHTS_USE_TEMPLATE macro.
  2. g++ -S -O0 {main}.cpp. Option ‘-S’ tells gcc to only compile (no assembling and linking), then find the mangled function and use c++filt to demangle it. For a quick lookup, refer to the function declaration section, which looks like:
.{label}:
    .size	{mangled_fnc_name}, .-{mangled_fnc_name}
    .section	.rodata
    .align {align}
  1. clang++ -Xclang -ast-print -fsyntax-only {main}.cpp. It generates very convenient and pretty (on my opinion) output without any online web services and manual demangling.

Sections 5 and 6 explain the benefits and challenges of using the auto keyword. Agree with all the rationale in the topic, but there are still several reasons to use explicit types. In a large project where auto deducing is interleaved with template type deducing, it is difficult to tell exactly what auto means in the declaration context or in function signature without deep dive into the callstack. It’s even harder to figure it out without an IDE, although it offten happens in small codereview. Secondly, it’s possible to forget that auto deduction discards cv-qualifier. And not only, any implicit convertion will be broken. I believe it’s not a regular problem but should be kept in mind.

Item 15 of chapter 3 exposes the benefits of using constexpr. It’s worth adding that in C++20 the power of constexpr keyword has significantly increased. One interesting case is the ability to allocate a dynamic memory at compile time. The only rule that dealocation should be done before returning from the top level function within translation unit. So this code will compile starting from C++20:

struct test { int getval() const noexcept { return 1; } }
constexpr
int foo()
{
    test* t = new test();
    int res = t.getval();
    delete t;
    return res;
}
static_cast(foo() == 1);

I highly recommend reading the fascinating article about constexpr by PVS-Studio Design and evolution of constexpr in C++

Chapter 4 covers the essense of smart pointers management. As usual, I won’t interfere with the existing description or comment on what has already been described but just add a few extra hints. At least something is already deprecated and new has been added since the book publication:

  • std::auto_ptr<> was completely removed starting from C++17. It was replaced by std::unique_ptr<>.
  • When we use std::shared_ptr{new char[BUFSIZE]} - std::default_delete policy will handle the array pointer with default object deleter delete ptr . There is no big problem with trivial types like int, char etc. But when a user-defined struct is used, there is no guarantee calling of the all objects destructors. If a default delete policy can satisfy the objects destroying, just advise to type deduction std::shared_ptr{new char[BUFSIZE], std::default_delete<char[]>()} is enough.
  • Starting from C++23 std::inout_ptr and std::out_ptr_t will be released. I’m not going to post the example because here is the nice short explanation of the behavior stackoverflow and here is the proposal itself p1123. If you are familiar with the C# keywords out and ref you can easely think the std::out_ptr_t and std::inout_ptr_t respectively. But it’s worth mentioning that this is only wrappers for the std::unique_ptr to pass it to C-like functions, and there’s no guarantee that underlying object will be set.

There are many more topics worthy of attention but assuming the impossibility of discussing everything, let’s leave that to the reader.

Have a good cup of coffee and enjoy reading.