C++ Language Excerpts


  • Variables and Basics
    1. The standard only defines minimum size of various types. (int 4, long long 8 etc)
    2. wchar_t should never be used. It varies across machines and cannot be relied on to be able to hold unicode. Use char32_t instead for Unicode code points.
    3. There are various type conversions possible and type conversions can happen in expresssions. For eg, when bool is assigned to an int or long long is assigned to long.
    4. Literals may have various types 1L (long), 1ll(long long), 1ULL(unsigned long long),L”C”(wchar_t string), u”C” (char16_t string), U”C” (char32_t string)
    5. Initialization( Type i = obj) is different from assignment ( Type i, j; i = j). To understand, think that for the first case a copy constructor is called, second case an “=” operator is called.
    6. List initialization of primitive type in C++11:

      int units_sold = 0;
      int units_sold = {0};
      int units_sold{0};
      int units_sold(0);

    7. Uninitialized objects of built-in type defined inside a function body have undefined value. Otherwise, they are generally zero initialized. Zero initialization
    8. The most direct approach is to initialize the pointer using the literal nullptr, which was introduced by C++11. nullptr is a literal that has a special type that can be converted to any other pointer type. Alternatively, we can initialize a pointer to the literal 0, as we do in the definition of p2.
    9. Reference to pointer:

      int *p; // p is a pointer to int
      int *&r = p; // r is a reference to the pointer p

      Note: It can be easier to understand complicated pointer or reference declarations if you read them from right to left.

    10. Const

      const int i = get_size(); // ok: initialized at run time
      const int j = 42; // ok: initialized at compile time
      const int k; // error: k is uninitialized constv
      int i = 0;
      int *const p1 = &i; // we cant change the value of p1; const is top-level
      const int ci = 42; // we cannot change ci; const is top-level
      const int *p2 = &ci; // we can change p2; const is low-level
      const int *const p3 = p2; // right-most const is top-level, left-most is not
      const int &r = ci; // const in reference types is always low-level

    11. A constant expression is an expression whose value cannot change and that can be evaluated at compile time.

      const int max_files = 20; // max_files is a constant expression
      const int limit = max_files + 1; // limit is a constant expression
      int staff_size = 27; // staff_size is not a constant expression
      const int sz = get_size(); // sz is not a constant expression

    12. C++ 11 introduces constexpr varible and function declaration that are evaluated at compile time

      constexpr int mf = 20; // 20 is a constant expression
      constexpr int limit = mf + 1; // mf + 1 is a constant expression
      constexpr int sz = size(); // ok only if size is a constexpr function

    13. auto : Deduce type from the expression C++11
    14. decltype: Sometimes we want to define a variable with a type that the compiler deduces from an expression but do not want to use that expression to initialize the variable. C++11

      decltype(f()) sum = x; // sum has whatever type f returns
      const int ci = 0, &cj = ci;
      decltype(ci) x = 0; // x has type const int
      decltype(cj) y = x; // y has type const int& and is bound to x
      decltype(cj) z; // error: z is a reference and must be initialized

    15. For class/struct declaration, we can supply an in-class initializer for a data member. When we create objects, the in-class initializers will be used to initialize the data members. Members without an initializer are default initialized. Thus, when we define Sales_data objects, units_sold and revenue will be initialized to 0, and bookNo will be initialized to the empty string.

      In-class initializers are restricted as to the form we can use: They must either be enclosed inside curly braces or follow an = sign. We may not specify an in-class initializer inside parentheses.

      struct Sales_data {
      std::string bookNo;
      unsigned units_sold = 0;
      double revenue = 0.0;
      };

  • String, Vectors and Arrays
    1. Using delarations should not be used in header files because it will cause unintended namespace pollution in files that include it.
    2. range for

       for (auto &c : s)   // for every char in s (note: c is a reference)  
           c = toupper(c); // c is a reference, so the assignment changes the char  
      
    3. List initialization of a vector

       vector<int> v1(10);    // v1 has ten elements with value 0  
       vector<int> v2{10};    // v2 has one element with value 10  
       vector<int> v3(10, 1); // v3 has ten elements with value 1  
       vector<int> v4{10, 1}; // v4 has two elements with values 10 and 1  
      
    4. begin and end functions to mimick iterator behavior for arrays:

       int ia[] = {0,1,2,3,4,5,6,7,8,9}; // ia is an array of ten ints  
       int *beg = begin(ia); // pointer to the first element in ia  
       int *end = end(ia);  // pointer one past the last element in ia  
       while (beg != end && *beg >= 0)  
           ++beg;  
      
  • Function Pointer

       bool lengthCompare(const string &, const string &);
       bool (*pf)(const string &, const string &);  // uninitialized
       string::size_type sumLength(const string&, const string&);
       bool cstringCompare(const char*, const char*);
       pf = 0;              // ok: pf points to no function
       pf = sumLength;      // error: return type differs
       pf = cstringCompare; // error: parameter types differ
       pf = lengthCompare;  // ok: function and pointer types match exactly
    
  • Expressions 1. casts:

    static_cast is generally used to do arithmetic type conversions explicitly. const_cast is used to cast away the constness of an object

       const char *cp;
       // error: static_cast cant cast away const
       char *q = static_cast<char*>(cp);
       static_cast<string>(cp); // ok: converts string literal to string
       const_cast<string>(cp);  // error: const_cast only changes constness
    

    reinterpret_cast is used todo low level reinterpretation.

       int *ip;
       char *pc = reinterpret_cast<char*>(ip);
    

    Old style type casts are same as reinterpret cast. They are less visible and their intent is less visible.

  • Copy Constructors

      class Foo { 
        public:
          Foo();
          Foo(const Foo&); // copy constructor 
      };
    

    Unlike a default contructor, a copy contructor is synthesized even if we define other constructors The type of each member determines how that member is copied: Members of class type are copied by the copy constructor for that class; members of built-in type are copied directly. Although we cannot directly copy an array, the synthesized copy constructor copies members of array type by copying each element. Direct Initialization vs Copy Initialization:

      string s(dots);  // Direct Initialization
      string s2 = dots; // Copy Initialization
    

    Non-reference parameters, returns, container inserts(push_back, insert etc) use copy contructors. Container emplace uses direct initialization

  • Copy Assignment operator

      class Foo { 
        public:
          Foo& operator=(const Foo&); // assignment operator 
      };
    
  • Destructor

      class Foo { 
        public:
        ~Foo(); // destructor };
    

    The destructor body is executed before the members get destroyed.

  • Using =default

      class Sales_data { public:
        // copy control; use defaults Sales_data() = default;
        Sales_data(const Sales_data&) = default; 
        Sales_data& operator=(const Sales_data &); 
        ~Sales_data() = default;
        // other members as before
      };
      Sales_data& Sales_data::operator=(const Sales_data&) = default;
    

    =default need not be defined in the declaration. It only affects how the function is generated, not the use of the function.

  • Using =delete

     struct NoCopy {
       NoCopy() = default; // use the synthesized default constructor NoCopy(const NoCopy&) = delete; // no copy 
       NoCopy &operator=(const NoCopy&) = delete; // no assignment 
       ~NoCopy() = default; // use the synthesized destructor
       // other members
     };
    

    =delete needs to be specified during declaration because it is used in generating the code that uses the function.

  • Move and RValue references

     class StrVec { 
     public:
       StrVec(StrVec&&) noexcept; // move constructor
       // other members as before };
       StrVec::StrVec(StrVec &&s) noexcept : /* member initializers */ { /* constructor body */ }
    
     X& X::operator=(X const & rhs); // classical implementation
     X& X::operator=(X&& rhs)
     {
       // Move semantics: exchange content between this and rhs
       return *this;
     }
    

If the rhs is a rvalue and a move assignment is defined, the move assignment gets used. std::move and std::swap are also defined to aid in this process for std types. It also makes sure that rhs is in a destroybale state.

The std::move function is used to convert a l-value to a r-value

        int &&rr3 = std::move(rr1);

std::noexept is used to tell the compiler that move will not throw an exception. This is essential if we make the type to be a member of a vector. vector can then fulfill its promise that push_back will not throw an exception during reallocation.

The compiler will synthesize a move constructor or a move-assignment operator only if the class doesn’t define any of its own copy-control members and if every nonstatic data member of the class can be moved. The compiler can move members of built-in type. It can also move members of a class type if the member’s class has the corresponding move operation:

move iterator Iterator adaptor that generates an iterator that, when dereferenced, yields an rvalue reference.

  • override and final inheritance

    struct B {
    virtual void f1(int) const; virtual void f2();
    void f3();
    };
    struct D1 : B {
    void f1(int) const override; // ok: f1 matches f1 in the base void f2(int) override; // error: B has no f2(int) function
    void f3() override; // error: f3 not virtual
    void f4() override; // error: B doesn't have a function named f4
    }
    
    struct D2 : B {
    // inherits f2() and f3() from B and overrides f1(int)
    void f1(int) const final; // subsequent classes can't override f1 (int)
    };
    struct D3 : D2 {
    void f2(); // ok: overrides f2 inherited from the indirect base, B
    void f1(int) const; // error: D2 declared f2 as final };
    
  • virtual functions and default arguments: Virtual functions that have default arguments should use the same argument values in the base and derived classes. Otherwise base class default argument gets used

  • Circumventing virutal dynamic calls: // calls the version from the base class regardless of the dynamic type of baseP double undiscounted = baseP->Quote::net_price(42);