skip to content
Casual Reasoning

C++ Short Notes

C++ is a huge language. Each of its functionalities need good amount of memory to remember every detail. These are short notes about what should be remembered.

Terminology

Parameter: Name of the input in function definition/declaration (exp: x and y in void func(int x, double y))

Argument: the value passed to a parameter. (exp: 1 and variable a for function call void func(1,a))

Type Conversions

Implicit type conversion through passing function parameter, only occurs if the value is passed by value, or passed by constant reference (i.e. X const &). For example passing by non-constant reference (i.e. X &) will not allow implicit type conversion since if value of the parameter is modified in the function, it cannot be reflected back to argument that is originally used.

Inheritance

Redefinition of base class’s virtual function doesn’t have to have exact same return type as long as returned types are covariant. See here for more details. Exp:

class Base{
  virtual Base* Clone() const;
}

class Derived: public Base{
  virtual Derived* Clone() const;
}

Concurrency

To call a callable only once use std::call_once. Concurrency safe and thread aware (i.e. even if multiple threads call std::call_once only one of them will be executed given that flag is shared). Exp:

std::once_flag flag;
std::call_once(flag, [](){std::cout<<"Called once";});

if concurrency is not a concern, you can leverage static variable and lambda functions to achieve the same effect. (I don’t like flag approach generally)

void func(){
  static bool _unused = [](){
    std::cout<<"Called once";
    return true;
    }();
}

(Smart) Pointers

Do not accept raw/smart pointer parameters to methods if not mandatory. Instead dereference the pointer and pass by reference. Same statement also holds for smart pointers. Unless the purpose is to share/transfer the ownership, the function doesn’t need to know about the ownership of the object, it just needs to use it. By accepting unnecessary shared_ptrs you are making the calling of the function a burden if the caller doesn’t have a shared_ptr to begin with. (i.e. it has a raw pointer or a reference). This is especially concerning for unit tests.


Use smart pointers if possible and use the right type: unique_ptr vs shared_ptr. Entity types should be unique_ptr unless they are shared among multiple entities. Value types should prefer shared_ptr to allow storage in multiple containers and copies when needed.


Do not return raw pointers (or as out parameter), it is hard to establish ownership context without additional metadata. (exp. who will deallocate the memory) There is no (clean) way of knowing what type of memory a pointer is pointing to. It can be containing address of a local variable, global variable, dynamically allocated variable.


Don’t return pointer to local variables.


Need a pointer and reference to a global variable? Prefer defining object as std::unique_ptr type to begin with and dereference to access the object, instead of defining as object and getting it’s pointer.

// negative.cpp
Object x(...);
...
...
{
  func1(&x);
  func2(x);
}

// positive.cpp
auto x = std::make_unique<Object>(...);
...
...
{
  func1(x.get());
  func2(*x);
}

C++ guarantees that nullptr is safe to delete.

Exceptions

You can catch exceptions (like passing function parameters) by value, reference or pointer


Unlike function parameters, object thrown as exception is copied w.r.t its static type (NOT dynamic type, i.e. polymorphic object thrown by reference of its parent type, will be copied as its parent type.)

  • This is because unlike function calls, control never returns to where exception is thrown, so the ownership of exception (in case of reference) cannot be maintained by throwing function.

Rethrowing:

  • Rethrowing an exception by throw e will cause e to be copied.
    • Object is copied as the caught type.
  • Rethrowing an exception by throw will only propagate captured exception without any new copy.
    • Preserves caught objects dynamic type.

Possible combinations and their ramifications:

  • Object thrown and caught by value. Object is copied twice, one due to throw rule mentioned above and second is due to capturing it by value.
  • Object thrown and caught by reference. Object is copied once.
  • Pointer thrown and caught by pointer. Pointer copied.
    • Throwing pointer is the only way of not copying exception object.

Exceptions will trigger stack unwinding, i.e. destructors of your local variables will be called.

  • Use smart pointers instead of raw pointers to avoid memory leaking due to exceptions.

A partially constructed object, (one that may thrown an exception in the middle) will never have its destructor called.

  • C++ destructors are only called for fully constructed objects.
  • In constructor exceptions of called methods (including new) should be caught and handled.

If an exception leaves the destructor while another exception is active (i.e. destructor is called due to another exception). C++ will call terminate.

  • You can still catch exceptions in destructor preventing them leaving it.

catch clause only catches the specified type with two exception conversions:

  • It will accept inheritance imposed conversions (a derived object can be caught by parent type)
  • It will accept typed to untyped pointer conversions (any exception pointer can be caught by void * type)

catch clauses will be tried in order of their appearance in the code. Catch order should be aware of inheritance among types (derived type should be caught earlier than parent type)


C++ standard exceptions are never thrown as pointers.


Most ideal way of handling exceptions is by catching them as references and rethrowing them by throw (as oppoes to throw e).


Unhandled exceptions at runtime cause invocations of unexpected, and unexpected method by default call terminate which by default call abort.

  • Default behaviour ends up causing no proper stack unwinding to occur

You can update the unexpected handler with your custom method to convert it into a known exception to prevent terminate getting called. (see set_unexpedted)

Object Oriented Programming

  • Motivation: Declaration of a class should only contain the information relevant to class user, not implementer.
  • Define private methods in class implementation file, instead of defining them part of the class.
    • Most (see below) private methods don’t communicate anything useful to class user.
    • Creating unit tests for private methods it too troublesome.
    // negative.h
    class X{
      public:
      void DoStuff();
      private:
      int a;
      void IncrementA();
    };
    // negative.cpp
    void X::IncrementA(){++a;}
    void X::DoStuff(){ IncrementA();}
    // negative_test.cpp
    // No way to test functionality of IncrementA
    
    
    ---------------------------------
    // positive.h
    class Y{
      public:
      void DoStuff();
      private:
      int a;
    };
    // positive.cpp
    int IncrementA(int a){++a;}
    void Y::DoStuff(){ a = IncrementA(a);}
    // positive_test.cpp
    int IncrementA(int); // Forward declare
    Test(){
      EXPECT_EQ(IncrementA(1),2);
    }
    In above example, even if the private X::IncrementA method would except all parameters,and/or be static, still due to it being private would prevent us from creating a unit test.
  • private meethods are there to communicate what functionality that is expected to be generally available, is forbidden.
    • exp: Hidden constructor, copy-constructor, assignment operator etc.
  • If a class is handling multiple tasks, break each task into its own class, i.e. decomposition. Decomposition helps with reducing coupling and incentivizes good interface.
    // negative.h
    class Vehicle{
      double velocity;
      double avg_velocity;
      double expected_velocity;
      double GetNextVelocity();
    
      Position target;
      Position current;
      double distance_from_origin;
      double distance_from_target;
      void IsAtTargetLocation();
    }
    
    // positive.h
    class VelocityManager{
      double velocity;
      double avg_velocity;
      double expected_velocity;
      double GetNextVelocity();
    }
    
    class PositionManager{
      Position target;
      Position current;
      double distance_from_origin;
      double distance_from_target;
      void IsAtTargetLocation();
    }
    
    class Vehicle{
      VelocityManager vm;
      PositionManager pm;
    }