QuantStack

Defining your own types

Update the repo

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

class - matrix.hpp


    class matrix
    {
    public:
        
        matrix(std::size_t nb_rows, std::size_t nb_cols);

    private:

        std::size_t m_nb_rows;
        std::size_t m_nb_cols;
        std::vector<double> m_data;
    };
                        

class - matrix.cpp


    matrix::matrix(std::size_t nb_rows, std::size_t nb_cols)
        : m_nb_rows(nb_rows),
          m_nb_cols(nb_cols),
          m_data(nb_rows * nb_cols)
    {
    }
                        

    int main(int argc, char* argv[])
    {
        dauphine::matrix m(2, 4);
        std::cout << m.m_nb_rows << std::endl;
        return 0;
    }
                        

class - matrix.hpp

Define methods that return m_nb_rows and m_nb_cols


    class matrix
    {
    public:

        matrix(std::size_t nb_rows, std::size_t nb_cols);

        std::size_t nb_rows() const;
        std::size_t nb_cols() const;
    };
                        

class - matrix.cpp


    std::size_t matrix::nb_rows() const
    {
        return m_nb_rows;
    }

    std::size_t matrix::nb_cols() const
    {
        return m_nb_cols;
    }
                        

    int main(int argc, char* argv[])
    {
        dauphine::matrix m(2, 4);
        std::cout << m.nb_rows() << std::endl;
        std::cout << m.nb_cols() << std::endl;
        return 0;
    }
                        

Const - 1/2


    std::size_t matrix::nb_rows() const
    {
        m_nb_rows = 0;
        return m_nb_rows;
    }

                        

Changing an attribute in a const method is forbidden

class - matrix

Define a method that resizes the matrix


    class matrix
    {
    public:

        void resize(std::size_t nb_rows, std::size_t nb_cols);
    };
                        

    void matrix::resize(std::size_t nb_rows, std::size_t nb_cols)
    {
        m_nb_rows = nb_rows;
        m_nb_cols = nb_cols;
        m_data.resize(m_nb_rows * m_nb_cols);
    }
                        

Const - 2/2


    int main(int argc, char* argv[])
    {
        const matrix m(2, 4);
        m.resize(3, 5); 
        return 0;
    }
                        

Calling a non-const method on a const object is forbidden

const rules

  • Whenever a parameter won't be modified, make it const
  • Whenever a method does not change the object, make it const
  • If you're not sure, make it const
  • Think twice before removing a const

matrix - access operator


    class matrix
    {
    public:

        double& operator()(std::size_t i, std::size_t j);
        const double& operator()(std::size_t i, std::size_t j) const;
    }
                        

    double& matrix::operator()(std::size_t i, std::size_t j)
    {
        return m_data[i * m_nb_cols + j];
    }

    const double& matrix::operator()(std::size_t i, std::size_t j) const
    {
        return m_data[i * m_nb_cols + j];
    }
                        

matrix - output operator


    #include <ostream>
    std::ostream& operator<<(std::ostream& out, const matrix& m);
                        

    std::ostream& operator<<(std::ostream& out, const matrix& m)
    {
        for(std::size_t i = 0; i < m.nb_rows(); ++i)
        {
            for(std::size_t j = 0; j < m.nb_cols(); ++j)
            {
                out << m(i, j) << ", ";
            }
            out << std::endl;
        }
        return out;
    }
                        

matrix - computed assignment


    class matrix
    {
    public:

        matrix& operator+=(const matrix& rhs);
        matrix& operator-=(const matrix& rhs);
        matrix& operator*=(const matrix& rhs);
        matrix& operator/=(const matrix& rhs);
    };
                        

matrix - computed assignment


    matrix& matrix::operator+=(const matrix& rhs)
    {
        for(std::size_t i = 0; i < m_nb_rows; ++i)
        {
            for(std::size_t j = 0; j < m_nb_cols; ++j)
            {
                m_data[i * m_nb_cols + j] = rhs.m_data[i * m_nb_cols + j];
            }
        }
        return *this;
    }
                        

    matrix& matrix::operator+=(const matrix& rhs)
    {
        std::transform(m_data.begin(), m_data.end(), rhs.m_data.begin(),
                       m_data.begin(), std::plus<double>());
        return *this;
    }
                        

matrix - computed assignment


    class matrix
    {
    public:

        matrix& operator+=(double rhs);
        matrix& operator-=(double rhs);
        matrix& operator*=(double rhs);
        matrix& operator/=(double rhs);
    };
                        

matrix - computed assignment


    matrix& matrix::operator+=(double rhs)
    {
        std::transform(m_data.begin(), m_data.end(), m_data.begin(),
                       [rhs](double arg) { return arg + rhs; });
        return *this;
    }
                        

matrix - arithmetic operators

Solution 1 (bad)


    class matrix
    {
    public:

        matrix operator+(const matrix& rhs) const;
        matrix operator+(double rhs) const;
    };

    matrix res = m1 + m2; // OK - equivalent to res = m1.operator+(m2);
    matrix res = m1 + 2.  // OK - equivalent to res = m1.operator+(2.);
    matrix res = 2. + m1  // Error, no overload found for operator+(double, matrix)
                        

matrix - arithmetic operators

Solution 2 (good)


    class matrix
    {
        // ...
    };

    matrix operator+(const matrix& lhs, const matrix& rhs);
    matrix operator+(const matrix& lhs, double rhs);
    matrix operator+(double lhs, const matrix& rhs);
                        

matrix - arithmetic operators


    matrix operator+(const matrix& lhs, const matrix& rhs)
    {
        matrix tmp(lhs);
        tmp += rhs;
        return tmp;
    }

    matrix operator+(double lhs, const matrix& rhs)
    {
        return rhs + lhs;
    }
                        

matrix - value semantic


    class matrix
    {
    public:

        matrix(std::size_t nb_rows, std::size_t nb_cols);
        ~matrix();
        matrix(const matrix&);
        matrix& operator=(const matrix&);
        matrix(const matrix&&);
        matrix& operator=(matrix&&);
    };
                        

matrix - value semantic


    matrix::matrix(const matrix& rhs)
        : m_nb_rows(rhs.m_nb_rows),
          m_nb_cols(rhs.m_nb_cols),
          m_data(rhs.m_data)
    {
    }
                        

The need for uvector

std::vector

  • initializes all its values upon construction
  • even when these values will be modified just after
  • not efficient for intensive computation on large matrices

The need for uvector


    matrix matrix::sigmoid(const matrix& m)
    {
        matrix res(m.nb_rows(), m.nb_cols());
        std::transform(m.m_data.begin(), m.m_data.end(), res.m_data.begin()
                       [](double arg) { return 1. / (1. + std::exp(-arg)); });
    }
                        

Digression - heap allocation


    double m_data[N];
    // N must be known during compilation
    // N is not mutable, you cannot resize m_data;
                        

    double* p_data = new double[size];
    // size can be computed at runtime
    delete[] p_data; // Never forget this
    delete[] p_data; // Never do it twice on a non null pointer
    p_data = nullptr;
    delete[] p_data; // OK 
                        

Digression - heap allocation

The survival guide to dynamic allocation

  • Default initialize pointers to nullptr
  • "new" must have a "delete" counterpart
  • Always reset a deleted pointer to nullptr
  • Avoid dynamic allocation as much as possible

uvector - constructor


    class uvector
    {
    public:

        uvector(std::size_t size);
        uvector(std::size_t size, double value);

    private:

        double* p_data;
        std::size_t m_size;
    };
                        

uvector - constructor


    uvector::uvector(std::size_t size)
        : p_data(nullptr), m_size(0)
    {
        if(size != 0)
        {
            p_data = new double[size];
            m_size = size;
        }
    }
                        

uvector - constructor


    uvector::uvector(std::size_t size, double value)
        : p_data(nullptr), m_size(0)
    {
        if(size != 0)
        {
            p_data = new double[size];
            m_size = size;
        }
        std::fill(p_data, p_data + size, value);
    }
                        

    uvector::uvector(std::size_t size, double value)
        : uvector(size) // delegating constructor
    {
        std::fill(p_data, p_data + size, value);
    }
                        

Delegating constructor


    class test
    {
    public:

        test(int i, int j) : m_i(i), m_j(j) {}
        test(int i) : test(i, 0) {} // OK
        test(int i) : test(i, 0), m_d1(0.), m_d2(0.) {} // Illegal
        test(double d1, double d2) : m_d1(d1), m_d2(d2) {}
        test(double d) : test(d, 0.) {} // Illegal, only one delegating constructor per class

    private:

        int m_i, m_j;
        double m_d1, m_d2;
    };
                        

uvector - Default constructor


    class uvector
    {
    public:

        uvector() : uvector(0) {}
    };
                        

    class uvector
    {
    public:

        uvector(std::size_t size = 0);
    };
                        

Destructor


    class uvector
    {
    public:

        uvector(std::size_t size = 0);
        uvector(std::size_t size, double value);

        ~uvector();
    };
                        

Destructor


    uvector::~uvector()
    {
        delete[] p_data;
        p_data = nullptr;
        m_size = 0;
    }
                        

Digression - heap allocation


    double* p1 = new double(size);
    double* p2 = p1;
                        

    delete p1;
    p2[2] // crash
                        

uvector - copy constructor


    class uvector
    {
    public:

        uvector(std::size_t size = 0);
        uvector(std::size_t size, double value);

        ~uvector();

        uvector(const uvector& rhs);
    };
                        

uvector - copy constructor


    uvector::uvector(const uvector& rhs)
        : p_data(nullptr), m_size(0)
    {
        if(rhs.m_size != 0)
        {
            p_data = new double[rhs.m_size];
            m_size = rhs.m_size;
            std::copy(rhs.p_data, rhs.p_data + m_size, p_data);
        }
    }
                        

uvector - assign operator


    class uvector
    {
    public:

        uvector(std::size_t size = 0);
        uvector(std::size_t size, double value);

        ~uvector();

        uvector(const uvector& rhs);
        uvector& operator=(const uvector& rhs);
    };
                        

uvector - assign operator


    uvector& uvector::operator=(const uvector& rhs)
    {
        delete[] p_data;
        p_data = new double[rhs.m_size];
        std::copy(rhs.p_data, rhs.p_data + rhs.m_size, p_data);
        m_size = rhs.m_size;
        return *this;
    }
                        

    uvector v(2, 2.);
    v = v;
                        

uvector - assign operator

Copy and swap idiom


    uvector& uvector::operator=(const uvector& rhs)
    {
        double* tmp = new double[rhs.m_size]; // Always allocate a temporary
        std::copy(rhs.p_data, rhs.p_data + rhs.m_size, tmp); // copy
        std::swap(tmp, p_data); // then swap
        delete[] tmp; // then delete the temporary
        m_size = rhs.m_size;
        return *this;
    }
                        

uvector - swap


    class uvector
    {
    public:

        void swap(uvector& rhs);

    private:

        double* p_data;
        std::size_t m_size;
    };

    void swap(uvector& lhs, uvector& rhs);
                        

uvector - swap


    void uvector::swap(uvector& rhs)
    {
        using std::swap;
        swap(p_data, rhs.p_data);
        swap(m_size, rhs.m_size);
    }

    void swap(uvector& lhs, uvector& rhs)
    {
        lhs.swap(rhs);
    }
                        

uvector - assign operator

Copy and swap idiom


    uvector& uvector::operator=(const uvector& rhs)
    {
        uvector tmp(rhs);
        swap(*this, tmp);
        return *this;
    }
                        

rvalue reference


    uvector compute(const uvector& param)
    {
        uvector res;
        // do some computation ...
        return res;
    }
                        

    // Inefficient copy
    uvector res = compute(my_huge_param);
                        

rvalue reference


    uvector& compute(const uvector& param)
    {
        uvector res;
        // do some computation ...
        return res; // Binding a temporary to a non const reference!
    }
                        

    // dangling reference
    uvector& res = compute(my_huge_param);
                        

rvalue reference


    uvector compute(const uvector& param)
    {
        uvector res;
        // do some computation ...
        return res;
    }
                        

    // rvalue reference
    uvector&& res = compute(my_huge_param);
                        

lvalue vs rvalue

lvalue

  • can be on both side of an assignment
  • can have a name (or not)
  • lvalue references bind to lvalues

rvalue

  • can be on right hand side of an assignment only
  • does not have a name
  • rvalue references bind to rvalues

lvalue vs rvalue


    uvector& func(uvector& t)
    {
        // do some stuff
        return t;
    }

    uvector t, something;
    func(t) = something;
                        

lvalue vs rvalue

  • short: If it has a name, it is an lvalue
  • long: http://en.cppreference.com/w/cpp/language/value_category

lvalue vs rvalue


    uvector compute(const huge_param& param);

    void function(const uvector& param);
    void function(uvector&& param);

    uvector m;
    function(m);
    function(compute(m));
    uvector&& ref = compute(m);
    function(ref);
                        

lvalue vs rvalue


    uvector compute(const huge_param& param);

    void function(const uvector& param);
    void function(uvector&& param);

    uvector m;
    function(m); // calls function(const uvector& param);
    function(compute(m)); // calls function(uvector&& param);
    uvector&& ref = compute(m); 
    function(ref); // calls function(const uvector& param);
                        

    function(static_cast<uvector&&>(ref));
    function(std::move(uvector));
                        

uvector - move semantic


    class uvector
    {
    public:

        uvector(std::size_t = 0);
        uvector(std::size_t size, double value);
        ~uvector();

        uvector(const uvector&)
        uvector& operator=(const uvector& rhs);

        uvector(uvector&&);
        uvector& operator=(uvector&& rhs);
    };
                        

uvector - move constructor


    uvector::uvector(uvector&& rhs)
        : p_data(std::move(p_data)), m_size(std::move(rhs.m_size))
    {
    }
                        

    uvector::uvector(uvector&& rhs)
        : p_data(std::move(p_data)), m_size(std::move(rhs.m_size))
    {
        rhs.p_data = nullptr;
        rhs.m_size = 0;
    }
                        

uvector - move assignment


    uvector& uvector::operator=(uvector&& rhs)
    {
        using std::swap;
        swap(p_data, rhs.p_data);
        swap(m_size, rhs.m_size);
        return *this;
    }
                        

Auto-generation rules

  • The default constructor is auto-generated if there is no:
    • user-declared constructor
  • The default destructor is auto-generated if there is no:
    • user-declared destructor

Auto-generation rules

  • The copy constructor is auto-generated if there is no:
    • user-declared move constructor
    • user-declared move assignment operator
  • The copy assignment operator is auto-generated if there is no:
    • user-declared move constructor
    • user-declared move assignment operator

Auto-generation rules

  • The move constructor is auto-generated if there is no:
    • user-declared copy constructor
    • user-declared copy assignment operator
    • user-declared destructor
  • The move assignment operator is auto-generated if there is no:
    • user-declared copy constructor
    • user-declared copy assignment operator
    • user-declared destructor

Auto-generation rules

If you need to declare and implement any of

  • destructor
  • copy constructor
  • copy assignment operators
  • move constructor
  • move assignment operator

... you probably need to implement all off them

Conversion


    class uvector
    {
    public:

        uvector(std::size_t size = 0);
        // ...
    };

    void compute(const uvector& v);

    compute(std::size_t(2));
                        

Conversion


    class uvector
    {
    public:

        explicit uvector(std::size_t size = 0);
        // ...
    };

    void compute(const uvector& v);

    compute(std::size_t(2)); // Error: implicit conversion forbidden, constructor is declared as explicit
                        

Conversion


    class optional
    {
    public:

        optional(double value, bool has_value = true);

    private:

        double m_value;
        bool m_has_value;
    };

    bool has_value(const optional& o) { return o.has_value(); }
    bool res = has_value(1.2);
                        

Conversion


    optional opt(1.2);
    // ...
    double res = opt;
                        

    class optional
    {
    public:

        operator double() const { return m_value; }
        // .... as previous
    };
                        

Struct vs class


    class Test
    {
        double m_value; // private by default
    };

    struct Test2
    {
        double m_value; // public by default
    };
                        

friend


    class uvector
    {
    private:

        double* p_data;
        std::size_t m_size;
    };

    void print_debug(const uvector& v)
    {
        // Cannot access v.p_data because it is private
        std::cout << v.p_data << std::endl;
    }
                        

friend


    class uvector
    {
    private:

        double* p_data;
        std::size_t m_size;

        friend void print_debug(const uvector&);
    };
                        

friend


    class uvector
    {
    private:

        double* p_data;
        std::size_t m_size;

        friend class B;
    };
                        

friend

  • Is not reciprocal
  • Is not transitive