1. Const pointer vs. Pointer to const

//const pointer, the pointer cannot point to any other address
double data{100.0};
double data2{200.0};
double* const pdata{&data};
// pdata = &data2; //cannot compile

//like a reference, 
double& rdata{data};

//pointer to const, the variable (which the pointer is pointing to) is a const
//like a const reference, 
const double cdata{123.0};
const double *pcdata{&cdata};
// *pcdata = 456.0; //cannot compile

//like a const reference
const double& rcdata(cdata);

2. Memory Allocation

  • The new and new[] operators allocate a block of memory in the free store—holding a single variable and an array, respectively—and return the address of the memory allocated.
  • You use the delete or delete[] operator to release a block of memory that you’ve allocated previously using either the new or, respectively, the new[] operator. You don’t need to use these operators when the address of free store memory is stored in a smart pointer.
  • Low-level dynamic memory manipulation is synonymous for a wide range of serious hazards such as dangling pointers, multiple deallocations, deallocation mismatches, memory leaks, and so on. Our golden rule is therefore this: never use the low-level new/new[] and delete/delete[] operators directly. Containers (and std::vector<> in particular) and smart pointers are nearly always the smarter choice!

3. String Initialization

//No initializer (or empty braces, {}):
std::string empty;                       // The string ""

//An initializer containing a string literal:
std::string proverb{ "Many a mickle makes a muckle." };   // The given literal

//An initializer containing an existing string object:
std::string sentence{ proverb };         // Duplicates proverb

//An initializer containing a string literal followed by the length of the sequence in the literal to be used to initialize the string object:
std::string part_literal{ "Least said soonest mended.", 5 };  // "Least"

//An initializer containing a repeat count followed by the character literal that is to be repeated in the string that initializes the string object (mind the round parentheses!):
std::string open_wide(5, 'a');           // "aaaaa"

//An initializer containing an existing string object, an index specifying the start of the substring, and the length of the substring:
std::string phrase{proverb, 5, 8};       // "a mickle"

4. Function Calling

  • Always use the type std::string_view instead of const std::string& for input string parameters.
  • The string_view does not provide a c_str() function to convert it to a const char* array. Luckily, it does share
    with std::string its data() function
  • Never return the address of an automatic, stack-allocated local variable from a function.
  • returning a reference from a function is the only way you can enable a function to be used (without dereferencing) on the left of an assignment operation
  • In modern C++, you should generally prefer returning values over output parameters. This makes function signatures and calls much easier to read. Arguments are for input, and all output is returned. The mechanism that makes this possible is called move semantics which ensures that returning objects that manage dynamically allocated memory—such as vectors and stringsno longer involves copying that memory and is therefore very cheap. Notable exceptions are arrays or objects that contain an array, such as std::array<> still better to use output parameters.
  • auto never deduces to a reference type, always to a value type. This implies that even when you assign a reference to auto, the value still gets copied. This copy will moreover not be const, unless you explicitly use const auto. To have the compiler deduce a reference type, you can use auto& or const auto&.

5. Preprocessing Directives

  • Never use static anymore to mark names that should have internal linkage; always use unnamed namespaces instead.
  • Using a #define directive to define an identifier that you use to specify a value in C++ code has three major disadvantages: there’s no type checking support, it doesn’t respect scope, and the identifier name cannot be bound within a namespace. In C++, you should always use const variables instead.
  • there is no cross-platform macro identifier to detect whether the current target platform uses 64-bit addressing. most compilers, however, do offer some platform-specific macro that it will define for you whenever it’s targeting a 64-bit platform. a concrete test that should work for the visual c++, gcc, and clang compilers, for instance, would look something like this:
#if _WIN64 || __x86_64__ || __ppc64__
// Code taking advantage of 64-bit addressing...
  • assert() is for detecting programming errors, not for handling errors at runtime.