c++

Reading Notes on C++ Primer (5e)

Posted by hnqiu on June 11, 2019

Variables

  • A pointer is an object, a reference is not.
  • To understand the type of a variable, read the definition from right to left. The closest symbol defines what the variable is.
  • const describes the type, constexpr describes the expression (during compile time).
  • When defining a reference parameter in a function, use reference to const if we are not going to modify the argument.
  • pointer to const annd const pointer are different, but reference to const is often abbreviated as const reference, because there is no such thing as a const or non-const reference - reference is not an object.
  • decltype(var) returns the type of its operand, but decltype((var)) (double parentheses) will always return the reference.
  • Use #pragma guard header.

Expressions

  • && has higher precedence than ||.
  • Use c++ style casts instead of c-style ones.

Functions

  • Check initializer_list
  • Define inline and constexpr functions in headers.

Exception and debug

  • Use exception handling throw, try-catch
  • NDEBUG, __func__, __FILE__, __LINE__

Class features

  • Use const member functions
  • Member functions defined in the class are implicitly inline
  • c++ constructors must succeed, because they cannot report failures
  • A const member function that returns *this as a reference has a reference-to-const return type
  • To construct a class, use constructor initialization instead of assignment
  • const members are declared in the class body, and initialized in constructor initialization list
  • Delegating constructor
  • friend
  • static class members are associated with the class, rather than with the objects. Thus, static member functions cannot use this.
  • static data members should be defined and initialized outside the class body, unless it’s constexpr.
  • static members can be used as default arguments

Standard containers

  • <lib_type>.size() returns a <lib_type>::size_type value, which is unsigned, so avoid using ints along with size().
  • lib_type<T> e2(e1) is equivalent to lib_type<T> e2 = e1.
  • string.c_str() returns a pointer to const char.
  • swap(v1, v2), emplace_back
  • string operations: append, replace, find, to_string, stoi, stod

Generic algorithms

  • stable_sort, unique
  • Predicates
  • lambda expressions

Dynamic memory

  • shared_ptr, unique_ptr
  • Use smart pointers rather than built-in pointers

Constructor and destructor

  • Default constructor: vector<T> v1(val) - direct initialization
  • Copy constructor: vector<T> v2(v1) (equivalent to vector<T> v2 = v1) - copy initialization
  • Assignment: vector<T> v3; v3 = v2; - copy-assignment operator, overloaded - The v3 = v2 here is equivalent to v3.operator=(v2).
  • When writing copy-assignment constructors, it is best to first save the right-hand operand (say R) to a local temporary before destroying any members of the left-hand operand (L). Such that even if R is the object L itself, we will still have a copy after freeing the memory.
  • Destructor: a class usually needs a user-defined destructor if it manages resources outside the class object. The destructor is used to free the allocated resources. In this case, we cannot use synthesized copy constructor. Therefore, if a class needs a user-defined destructor, it needs to define a copy constructor and copy-assignment operator as well.
  • =delete flag indicates the member function is inaccessible.
  • If a class has const members, the compiler will not synthesize a default copy-assignment operator for it.
  • To reallocate (move) an object without copying it, we first need to use std::move to get the rvalue reference to the object, then we define a move constructor, whose parameter is an rvalue reference Class &&.
  • If a class has defined its copy constructor, copy-assignment operator or destructor, the compiler will not synthesize the move operations at all. If we do not define a move constructor, the object will be copied, even if we use std::move to get the rvalue reference as the argument. In this case, the rvalue reference will be implicitly converted to a const lvalue reference.

OOP

  • Inheritance: base and derived class, protected members
  • Keywords: virtual in base class and override in derived class
  • Base classes should have a virtual destructor; constructors, on the other hand, should not be virtual.
  • There are two kinds of member functions in base classes, one that would be inherited directly by the derived without any changes, the other that would be overridden, which are virtual functions.
  • We use a reference or pointer to the base class to call virtual functions, such that we can use the base object or the derived object as the argument - this mechanism is called dynamic binding.
  • The reference or pointer to the base can be bound to a derived object - there will be an implicit derived-to-base conversion.
  • Derived-class objects should not assign values to base members. Use interface instead.
  • The derived-to-base conversion only applies for conversions to a reference or pointer type. When initializing or assigning a base object with a derived object, we are actually calling the constructor or assignment operator of the base class. When this is happening, we only copy the base part members of the derived object and the derived-class part is sliced down.
  • Abstract base class has pure virtual functions, which have =0 flag.
  • The derived destructor is run first, then the base-class destructor - opposite order from the way they are constructed.
  • Base and derived objects cannot be stored in ONE container, so use containers to hold the (smart) pointers to these objects.
  • Base pointers cannot access derived class members, even if the pointer is pointed to a derived class object. However, if we know for sure that it is a derived object, we can access it through a cast:
    static_cast<Derived*>(basepointer)->derived_member;
    

Template

  • Templates should be declared and defined in the header file, along with declarations of any names used.
  • A template will generate code only when it is instantiated.
  • A function template has a parameter list, in which the parameters are separated by commas ,:
    template <typename T>
    T foo(T *p) {
        T tmp = *p;
        // ...
        return tmp;
    }
    
  • Template type parameters are used as type specifiers, followed by the keyword typename or class; nontype parameters represent a value rather than a type.
  • In a function template, better define parameters as references to const.
  • A class template looks like:
    template <typename T> class XXX {
    private:
        /* data */
    public:
        /* member functions */
        // ...
    }
    
  • A list of explicit template arguments are required to be bound to the class template’s parameters during instantiation.
  • Members of an instantiated class template will be instantiated only when they are used.
  • A nontemplate friend of a class template will have friend access to all instantiations; a template friend will have a controlled friendship, which can include all instantiations or some specific instantiations.
  • Explicit instantiation and specialization
    // definition
    template <typename T> T* foo(T *p) {
      // ...
    }
    
    // explicit instantiation
    template int* foo<int>(int *p);
    // specialization
    template <> int* foo<int>(int *p) {
      // ...
    }