QuantStack

Pointers and memory

Update the repo

  • git checkout master
  • git pull upstream master
  • git push origin master
  • git checkout -b pointers

3 kinds of memory

  • Static memory
  • Automatic (stack-allocated) memory
  • Dynamic (heap-alocated) memory

Static memory

  • Global variables
  • Initialized when the program starts (before entering the main)
  • No guaranteed order of initialization
  • Life duration: whole program execution

Static memory


    // resource.cpp
    resource r; // Definition of global object declared in resource.hpp
                        

    // manager.cpp
    manager m; // Definition of global object declare in manager.hpp
    
    manager::manager()
    {
        r.acquire();
    }
                        

Static memory


    // resource.cpp
    resource& r()
    {
        static resource res;
        return res;
    }
                        

    // manager.cpp
    manager m; // Should be wrapped in a function too

    manager::manager()
    {
        r().acquire();
    }
                        

Static memory


    class manager
    {
    public:

        void some_method
        {
            m_resource.acquire();
            // ...
        }

    private:

        static resource m_resource;
    };
                        

Static memory


    class manager
    {
    public:

        void some_method
        {
            get_resource().acquire();
            // ...
        }

    private:

        static resource& get_resource();
    };
                        

Automatic memory

  • Memory used for local variables
  • Initialization order is guaranteed
  • Life duration: scope

Dynamic memory

  • Memory allocated with specific operator
  • Life duration: needs to be explicitly freed

Pointers


    void test_pointers()
    {
        double d = 0.5;
        double* p = &d
        std::cout << d << std::endl;
        std::cout << *p << std::endl;
        std::cout << p << std::endl;
        int i = 4;
        double* p2 = &i; // Error: cannot convert from int* to double*
    }
                        

Pointers


    void test_reinterpret_cast()
    {
        int i = 4;
        int* pi = &i;
        // double* d = (double*)pi;
        double* d = reinterpret_cast<double*>(pi);
        std::cout << i << std::endl;
        std::cout << pi << std::endl;
        std::cout << d << std::endl;
        std::cout << *pi << std::endl;
        std::cout << *d << std::endl;
    }
                        

Dynamic allocation


    int* i = new int; // allocates an uninitialized integer
    int* i2 = new int(5); // allocates an integer initialized to 5
    int* i3 = new int[5]; // allocates an array of 5 integers

    delete[] i3; // free the memory used by the aray i3
    delete i2;   // free the memory used by i2
    delete i;    // free the memory used by i
                        

Dynamic allocation


    resource* p = new resource;
    // Equivalent to
    void* mem = malloc(sizeof(resource)); // allocates memory
    resource* p = new (mem) resource; // Calls resource constructor

    delete p;
    // Equivalent to
    p->~resource(); // calls resource destructor
    free(p); // free  memory
                        

Pointer arithmetic


    int* ar = new int[10];
    // Or
    std::vector<int> v(10);
    int* ar = v.data();

                        

    int* ar2 = ar + 2;
    std::cout << *ar2 << std::endl;
    std::cout << ar[2] << std::endl;
    std::cout << (ar2 - ar) << std::endl;

    int* ar3 = ar2 - 1;
    std::cout << *ar3 << std::endl;
    std::cout << ar[1] << std::endl;
                        

Exceptions


    void mean(const std::vector<double>& param)
    {
        if(param.size() == 0)
        {
            throw std::runtime_error("param size is 0")
        }
        else
        {
            // ...
        }
    }
                        

Exceptions


    try
    {
        std::vector<double> v(0);
        mean(v);
    }
    catch(std::exception& e)
    {
        std::cout << "caught exception - " << e.what() << std::endl;
    }
                        

Exceptions

Compile and run the program in cpp folder

Fix the test_resource function

Exceptions


    void test_resource()
    {
        resource r;
        try
        {
            r.acquire();
            r.print_message();
            r.release();
        }
        catch(std::exception& e)
        {
            std::cout << "exception caught: " << e.what() << std::endl;
            r.release();
        }
    }
                        

Exceptions - RAII

Resource Acquisition Is Initialization


    void test_resource()
    {
        resource r;
        resource_guard g(r);
        r.print_message();
    }
                        

Exceptions - RAII


    class resource_guard
    {
    public:

        resource_guard(resource& r);
        ~resource_guard();

    private:

        resource& m_r;
    };
                        

Exceptions - RAII


    resource_guard::resource_guard(resource& r)
        m_r(r)
    {
        m_r.acquire();
    }

    resource::~resource_guard()
    {
        m_r.release();
    }
                        

Exceptions - noexcept


    void function() noexcept;
                        

The noexcept keyword

  • is part of the signature
  • tells the compiler that the function does not raise
  • the compiler does not check the validity of noexcept
  • is mainly used for performance purpose

Memory Management

Define the ownership


    underlying::~underlying()
    {
        delete p_volatility;
        p_volatility = nullptr;
    }

    option::~option()
    {
        p_underlying = nullptr;
        p_volatility = nullptr;
    }
                        

Memory Management


    // SOLUTION 1
    using volatility_ptr = std::unique_ptr<volatility>;
    volatility_ptr make_volatility()
    {
        return volatility_ptr(new volatility);
    }
                        

    // SOLUTION 2
    using volatility_ptr = std::unique_ptr<volatility>;
    volatility_ptr make_volatility()
    {
        return std::make_unique<volatility>();
    }
                        

Memory Management


    underlying::underlying(volatility_ptr vol)
        : p_volatility(std::move(vol))
    {
    }

    underlying::~underlying()
    {
    }

    volatility_ptr underlying::get_volatility() const
    {
        return p_volatility.get();
    }
                        

Memory Management


    void test_volatilty
    {
        volatility_ptr vol = make_volatility();
        underlying u(std::move(vol));
        // Cannot use vol since it has been moved
        // option opt(&u, vol);
        option opt(&u);
    }
                        

Memory Management


    option::option(underlying* u)
        : p_underlying(u), p_volatility(nullptr)
    {
        p_volatility = p_underlying->get_volatility();
    }

    option::~option()
    {
        // No ownership
        p_underlying = nullptr;
        p_volatility = nullptr;
    }
                        

shared_ptr


    std::shared_ptr<int> p = new int;
    std::shared_ptr<int> p2(new int);
    auto p3 = std::make_shared<int>();
                        

shared_ptr


    void function()
    {
        auto p = std::make_shared<int>(); // ref_count = 1;
        {
            std::shared_ptr<int> p2 = p; // ref_count = 2;
            // ...
        } // p2 destructor is called, ref_count = 1
    } // p destructor is called, ref_count = 0, deletes the internal pointer
                        

shared_ptr


    class product
    {
    public:

        void set_data_model(data_model_ptr);
    
    private:

        data_model_ptr p_data;
    };
                        

shared_ptr


    void product::set_data_model(data_model_ptr)
    {
        p_data = data_model_ptr;
    }

    pricer::pricer(product_ptr prod, data_model_ptr data)
        : p_product(prod), p_data(data)
    {
        p_product->set_data_model(p_data);
        std::cout << "pricer constructor" << std::endl;
    }
    
                        

shared_ptr

  • is not a garbage collector
  • should not replace a clear memory owneship design
  • should be used in a multithreaded context only