CPP - shivamvats/notes GitHub Wiki

C++

  1. Never use using namespace foo directive (except in small executables).

  2. Return Value Optimization: If a function returns an object of a class and that returned object is assigned to a new variable in the calling function, the compiler performs RVO and does not copy the object twice. Instead it passes the address of the object to the calling function under the hood. However, the called function must only be creating and returning a single object of that particular class.

  3. std::move(foo): Marks foo as a temporary and when the constructor is called, the original object is left in an undefined state:

    std::vector<int> foo = {1, 2, 3, 4};
    std::vector<int> bar = std::move(foo);
    
  4. Conversion Constructor: It is a single-parameter constructor that is declared without the keyword explicit. The compiler uses the constructor to convert objects of the type of the first parameter to the type of the conversion constructor's class. Eg:

    class MyClass {
        MyClass( int a );
    };
    MyClass obj = 5;
    
  5. Compile Time Assert: Use static_assert for compile time asserts.

  6. Function definition in headers: Always declare such functions as inline or use macros. Not doing so will trigger a duplicate symbol linking error.

  7. constexpr: Using constexpr with a function/variable evaluates it at compile time. The idea is to do computations at compile time. Declaring a variable as static constexpr allows its use as a constant for all objects of the class.

  8. One Definition Rule(ODR)-use: A static constexpr member has a value upon its initialization in the class but does not have a memory address until it is defined outside the class. Hence, if the address of the class object (or reference) is ever used, then the member must be defined outside the class.

  9. Template Meta-programming: Do computation at compile time by using templates. (Stub)

  10. Compile-time Values: A lot of times, we would like to make use of the fact that the value of a function/variable is known at compile time (and it doesn't change during run-time). This helps the compiler optimize the code.

In C++03, your could either use macros or:

static const int a = 1;
static const int b = 2;
static const int sum_a_b = a + b;

Using static const allows the compiler to do all these computations at compiler time, making the code faster during run-time. As you can see, the compiler allows basic arithmetic as well.

In C++14, you should use constexpr.

  1. unique_ptr: You can't push_back a unique_ptr object into a vector as there can be only one copy of the pointer. Instead, use std::move.
  2. mutable: A const method in a class cannot modify any member variable. However, if you do need it to modify a variable, mark the variable as mutable in its declaration. This will allow you to modify only that variable and be const-correct.
  3. gsl::owner<T*> Use this when returning a raw pointer if you are also transferring the ownership of the pointer. This template doesn't do anything as it is just an alias for T. It is to be used only to let the coder know that he has got ownership of the pointer and needs to clean it up.
  4. static functions:
    1. Can be used in a cpp file to guarantee that the function is never used from any other file
    2. Can put a function with the same name in each translation unit, and they can all do different things. For instance, you could put a static void log(const char*) {} in each cpp file, and they could each all log in a different way.

Memory Allocation

  1. malloc: It is a library function in C, also available in C++, that allocates memory for N blocks at run time. malloc returns an object of the type void* and hence the returned pointer needs to be cast the right type.

  2. new: It is an operator in C++ that allocated memory for N blocks and also calls the objects constructor. It returns a pointer to an object of the right type.

  3. Placement new: In the default case, new allocates memory at some memory address and calls the constructor. The placement version of new allows us to pass a pointer to a pre-allocated memory address and only object construction is done. Eg:

    // Allocate on stack.
    unsigned char buf[sizeof(int)*2] ;
    
    // placement new in buf
    int *p = new (buf) int(3);
    int *q = new (buf + sizeof (int)) int(5);
    

*p gets a value of 3, while *q gets 5.flexible array member

  • new is faster that malloc.
  1. Flexible Array Member: Allows a struct to have a member array of dynamic size that does not need memory re-allocation:

    struct Vector {
        std::string name;
        // The flexible member must be the last.
        int data[1]; // Size of 1 required to be C++ legal.
    };
    
    Vector* v = malloc(sizeof(Vector) + 5*sizeof(int));
    for(int i=0; i < 5; i++)
        new (&v->data[i]) int;
    

C++14/11 Features

(A much bigger list is at https://github.com/AnthonyCalandra/modern-cpp-features)

  1. Return Type Deduction of Functions: If all the return pathways return objects of the same type, then the compiler can deduce the correct return type:

    auto sum( int a, int b ){
        if( a > 0)
            return a - b;
        else
            return a + b;
    }
    
  2. Non Static Data Member Initialization:: You can do this:

    class Man{
        public:
        Man(){};
        std::string name {"Shivam"};
    };
    
  3. Uniform/Curly Initialization: In C++, we typically call the class constructor using round brackets: Man man("Ram"); and initialize aggregate data-types using curly braces: std::vector<int> nums = { 1, 2};. Uniform initialization can replace both of these with the same syntax like so:

    Man man{"Ram"};
    std::vector<int> num{1, 2};
    

Neat eh! Use uniform initialization everywhere.

  1. decltype: Get the declared type of an object.:

    struct S{
        int x;
    };
    struct s;
    decltype(s->x) a;
    
  2. Trailing Return Type: Express the return type of a function in terms of the type of function parameters:

    template<class T, class U>
    auto multiply(T const& lhs, U const& rhs) -> decltype(lhs * rhs) {
          return lhs * rhs;
    }
    
  3. Parameter Packs and Variadic Templates: A parameter pack is a template that can be instantiated with any number of parameters. A template with at least one parameter pack is called a variadic template. For example, you could use parameter packs to define functions that take in an arbitrary number of arguments:

    template <typename T>
    T add(T arg){return arg;}
    
    template <typename T, typename... Ts>
    T add(T first, Ts... args){return first + add(args...);}
    
  4. std::variant<...>: It is C++'s type-safe implementation of a union of types.

    std::variant< std::vector<double>, int > var;
    var = -1; //no vector could be computed
    auto var_vec = std::get_if< std::vector<double> >(&var);
    if(var_vec == nullptr)
        std::cerr<<"No vector could be found\n";
    

Inheritance

  1. Consider a Base class with one Derived class. When we call the constructor of the Derived class, a Base constructor must be called first. This is followed by the Derived class's initializer list and then the Derived constructor's function body. What this means is that we cannot delay the call of the Base constructor.

This need may arise if for example the Derived constructor's parameters need to be modified to call the Base constructor. One fix for this situation is to have functions that modify each of those parameters and call those functions in an explicit call of the Base constructor in the Derived class constructor's initializer list. Eg:

TwoDimGridSpace::TwoDimGridSpace(
        const cv::Mat& img_,
        const int connectivity_,
        const double pixel_res_,
        const cv::Point& start_ ):
    LatticePlanningSpace(
            constructOccGrid( img_, pixel_res_ ),
            constructActionSpace( pixel_res_, connectivity_ ),
            std::vector<double> {pixel_res_*start_.x,
            pixel_res_*start_.y } ),
    m_connectivity( connectivity_ ){}
  1. Name Hiding: Consider a situation in which the base and the derived classes overload the same function name. You would assume that the derived class can access the base class's overloads. However, this is not the default behaviour. C++ hides the base class's overloads and the derived class begins with a clean slate. This behaviour can be overridden by using Base::foo; which allows the compiler to resolve the overloaded call to a base overload.
⚠️ **GitHub.com Fallback** ⚠️