Notes for C++

  1. push_back() vs emplace_back()
    • copy vs move
    • vec.insert({a, b}) vs vec.emplace(a, b)
  2. const vs constexpr
    • const : could be compile-time or runtime constants
    • constexpr : enforces compile-time constants. Fxns might still be runtime, check next point.
    • Any variable that should not be modifiable after initialization and whose initializer is known at compile-time should be declared as constexpr.
    • Any variable that should not be modifiable after initialization and whose initializer is not known at compile-time should be declared as const.
    • https://www.learncpp.com/cpp-tutorial/compile-time-constants-constant-expressions-and-constexpr/
    • Don’t use const when passing by value.
    • Don’t use const when returning by value.
  3. constexpr and consteval. Link
    • constexpr
      • can be called by both compile-time and runtime functions.
      • to ensure compile-time, capture the return value in a new constexpr variable.
    • consteval : – C++20 –
      • cannot be called during runtime
      • enforces compile-time call. Parameters should be constexpr.
  4. Lambda
  5. Variable initialisation.
    • 3 ways:
      • Copy (=) : Copy values.
      • Direct (()) : Direct initializer.
      • Brace ({}) : Direct list initializer. <--- use this
    • Default value for uninitialised variable:
     int a;    // bad: undetermined. could be anything.
     int b();  // bad: interpreted as a function. Will return true!
     int c{};  // good: init to 0
     int d{4.5}; // [error][but safer]. {} doesn't allow value which d can't hold. Doesn't drop '.5' automatically like other 2 methods
     string s; // empty string of size 0 i.e. s = ""
    
  6. Postfix operator has higher precedence than prefix operator.
     *ptr++ // increment ptr but deref the un-incremented address
    
  7. Operators which are mainly used for their ‘side effects’ return their left operand. This allows chaining.
     x = y = 5; // evaluates as x = (y = 5)
     cout << "Hello " << "world"; // evaluates as (std::cout << "Hello ") << "world!"
    
  8. Macros are bad:
    • Can’t be treated as normal variables. So, hard time to debug with debuggers; would see the name & not the actual value. Debuggers ‘watch’ these variables.
    • Don’t follow normal scoping rules; might end up getting used somewhere in the code!
    • Risk of a variable being defined as a macro in some file.
  9. Vector reserve() vs resize() vs nothing!:
    • reserve would allocate memory ahead of time. Capacity will be affected, not size.
    • resize would insert or delete elements. Size is affected. Capacity may not be if new size <= old capacity.
    • nothing would grow/double the vector when size>capacity. Required O(N) copying everytime!!
  10. lvalue vs rvalue:
    • lvalues : variables/objects that persist beyond the end of the expression.
    • rvalues : literals or returned value of functions/operators that are discarded at the end of expression.
  11. pointers(*) vs reference(&): Great answer link
    • pointer: First account forwarding to second account. The pointer itself has a memory location.
      • name1@email.com -> name_surname@email.com
    • reference: Alias of email. Both are same account; no separate memory.
      • netID@uni.com, first.last@uni.com
      • has to be initialised. can’t be reseated.
  12. L-value reference with const:
    int x { 50 };
    const int y {100};
    
    int& ref { x };           // valid: lvalue reference bound to a modifiable lvalue
    const int& const_ref {x}; // still valid
    
    // const_ref = 51 // invalid: can't change through const reference.
    x = 51;           // valid: can be changed through non-const identifier 
    cout << << ref << " " << const_ref << endl; // 51 51
    
    int& ref_y { y };           // invalid: can't bind to a non-modifiable lvalue
    const int& ref_y_const {y}; // valid
    
    int& ref_rvalue {1};        // invalid: can't bind to an r-value. Else you'll try to modify 1 using the reference
    const int& ref_rvalue2 {1}; // valid: temporary object of rvalue is created.
    
  13. const ptr : compiler-explorer link
        const MyClass* ptr = &obj;
        MyClass* const ptr = &obj;
        const MyClass* const ptr = &obj; //read-only
    
  14. unique_ptr and shared_ptr:
    • unique_ptr
    • shared_ptr : multiple smart pointers share the same resource. Resource is deallocated when the last smart ptr goes out of scope (count==0)
      • uses reference counting for control block.
      • prefer make_shared. Combines resource + control into a sigle memory.
  15. Caution:
    int decimal = 10;
    int binary = 0b1010;
    int hex = 0x1010;
    int octal = 010; // <-- prefix zero means Octal!!
    
  16. Python-like bindings:
    for(auto [key, val]: my_map){
        cout << key << ": " << val << '\n';
    }
    
  17. Classes:
    • Compiler Explorer code link
    • Default access to members and methods is private
    • classes with virtual functions maintain vtbl (virtual function table) to determine the function call
  18. Dynamic & Static cast:
        if(TypeA* p = dynamic_cast<TypeA*>(ptr)){
            cout << "ptr is of TypeA" << endl;
        }
    
        int a = static_cast<int>(3.4);
    
  19. Move vs Copy - Compiler Explorer, Quick C++ Benchmark