Reader small image

You're reading from  Advanced C++

Product typeBook
Published inOct 2019
Reading LevelIntermediate
Publisher
ISBN-139781838821135
Edition1st Edition
Languages
Right arrow
Authors (5):
Gazihan Alankus
Gazihan Alankus
author image
Gazihan Alankus

Gazihan Alankus holds a PhD in computer science from Washington University in St. Louis. Currently, he is an assistant professor at the Izmir University of Economics in Turkey. He teaches and conducts research on game development, mobile application development, and human-computer interaction. He is a Google developer expert in Dart and develops Flutter applications with his students in his company Gbot, which he founded in 2019.
Read more about Gazihan Alankus

Olena Lizina
Olena Lizina
author image
Olena Lizina

Olena Lizina is a software developer with 5 years experience in C++. She has practical knowledge of developing systems for monitoring and managing remote computers with a lot of users for an international product company. For the last 4 years, she has been working for international outsourcing companies on automotive projects for well-known automotive concerns. She has been participating in the development of complex and high-performance applications on different projects, such as HMI (Human Machine Interface), navigation, and applications for work with sensors.
Read more about Olena Lizina

Rakesh Mane
Rakesh Mane
author image
Rakesh Mane

Rakesh Mane has over 18 years of experience in the software industry. He has worked with proficient programmers from a variety of regions such as India, the US, and Singapore. He has mostly worked in C++, Python, shell scripting, and database. In his spare time, he likes to listen to music and travel. Also, he likes to play with, experiment with, and break things using software tools and code.
Read more about Rakesh Mane

Vivek Nagarajan
Vivek Nagarajan
author image
Vivek Nagarajan

Vivek Nagarajan is a self-taught programmer who started out in the 1980s on 8-bit systems. He has worked on a large number of software projects and has 14 years of professional experience with C++. Aside from this, he has worked on a wide variety of languages and frameworks across the years. He is an amateur powerlifter, DIY enthusiast, and motorcycle racer. He currently works as an independent software consultant.
Read more about Vivek Nagarajan

Brian Price
Brian Price
author image
Brian Price

Brian Price has over 30 years experience working in a variety of languages, projects, and industries, including over 20 years' experience in C++. He was worked on power station simulators, SCADA systems, and medical devices. He is currently crafting software in C++, CMake, and Python for a next-generation medical device. He enjoys solving puzzles and the Euler project in a variety of languages.
Read more about Brian Price

View More author details
Right arrow

Chapter 3 - The Distance between Can and Should – Objects, Pointers and Inheritance

Activity 1: Implementing Graphics Processing with RAII and Move

In this activity, we will develop our previous Matrix3d and Point3d classes to use a unique_ptr<> to manage the memory associated with the data structures that are required to implement these graphics classes. Let's get started:

  1. Load the prepared project from the Lesson3/Activity01 folder and configure the Current Builder for the project to be CMake Build (Portable). Build and configure the launcher and run the unit tests. We recommend that the name that's used for the tests runner is L3A1graphicstests.
  2. Open point3d.hpp and add the lines marked with a comment to the file:

    // ... lines omitted

    #include <initializer_list>

    #include <ostream>

    namespace acpp::gfx { // Add this line

    class Point3d

    {

    // ... lines omitted

    };

    } // Add this line

    Note that the closing brace that's added to the end of the file does NOT have a closing semi-colon. The nested namespace syntax acpp::gfx, is a new feature of C++17. Previously, it would have required the explicit use of the namespace keyword twice. Also, beware that, in trying to be helpful, your friendly neighborhood IDE may insert the closing brace just after the line that you put the namespace declaration.

  3. Repeat the same treatment for matrix3d.hpp, matrix3d.cpp, and point3d.cpp – ensure that the include files are not included in the scope of the namespace.
  4. In the respective files (main.cpp, matrix3dTests.cpp, and point3dTests.cpp), just after completing the #include directives, insert the following line:

    using namespace acpp::gfx;

  5. Now, run all the tests. All 18 existing tests should pass again. We have successfully put our classes into a namespace.
  6. Now we will move onto converting the Matrix3d class to use heap allocated memory. In the matrix3d.hpp file, add an #include <memory> line to give us access to the unique_ptr<> template.
  7. Next, change the type of the declaration for m_data:

    std::unique_ptr<float[]> m_data;

  8. From this point forward, we will use the compiler and its errors to give us hints as to what needs fixing. Attempting to build the tests now reveals that we have a problem with the following two methods in the header file

    float operator()(const int row, const int column) const

    {

        return m_data[row][column];

    }

    float& operator()(const int row, const int column)

    {

        return m_data[row][column];

    }

    The problem here is that unique_ptr holds a pointer to a single dimension array and not a two- dimensional array. So, we need to convert the row and column into a single index.

  9. Add a new method called get_index() to get the one-dimensional index from the row and column and update the preceding functions to use it:

    float operator()(const int row, const int column) const

    {

        return m_data[get_index(row,column)];

    }

    float& operator()(const int row, const int column)

    {

        return m_data[get_index(row,column)];

    }

    private:

    size_t get_index(const int row, const int column) const

    {

        return row * NumberColumns + column;

    }

  10. After recompiling, the next error from the compiler refers to the following inline function:

    inline Matrix3d operator*(const Matrix3d& lhs, const Matrix3d& rhs)

    {

        Matrix3d temp(lhs);   // <=== compiler error – ill formed copy constructor

        temp *= rhs;

        return temp;

    }

  11. Whereas before, the default copy constructor was sufficient for our purposes, it just did a shallow copy of all the elements of the array and that was correct. We now have indirection to the data we need to copy and so we need to implement a deep copy constructor and copy assignment. We will also need to address the existing constructors. For now, just add the constructor declarations to the class (adjacent to the other constructors):

    Matrix3d(const Matrix3d& rhs);

    Matrix3d& operator=(const Matrix3d& rhs);

    Attempting to build the tests will now show that we have resolved all the issues in the header file, and that we can move onto the implementation file.

  12. Modify the two constructors to initialize unique_ptr as follows:

    Matrix3d::Matrix3d() : m_data{new float[NumberRows*NumberColumns]}

    {

        for (int i{0} ; i< NumberRows ; i++)

            for (int j{0} ; j< NumberColumns ; j++)

                m_data[i][j] = (i==j);

    }

    Matrix3d::Matrix3d(std::initializer_list<std::initializer_list<float>> list)

        : m_data{new float[NumberRows*NumberColumns]}

    {

        int i{0};

        for(auto it1 = list.begin(); i<NumberRows ; ++it1, ++i)

        {

            int j{0};

            for(auto it2 = it1->begin(); j<NumberColumns ; ++it2, ++j)

                m_data[i][j] = *it2;

        }

    }

  13. We now need to address the single-dimensional array look-up. We need to change the statements of the m_data[i][j] type with m_data[get_index(i,j)]. Change the default constructor to read like so:

    Matrix3d::Matrix3d() : m_data{new float[NumberRows*NumberColumns]}

    {

        for (int i{0} ; i< NumberRows ; i++)

            for (int j{0} ; j< NumberColumns ; j++)

                m_data[get_index(i, j)] = (i==j);          // <= change here

    }

  14. Change the initializer list constructor to be the following:

    Matrix3d::Matrix3d(std::initializer_list<std::initializer_list<float>> list)

          : m_data{new float[NumberRows*NumberColumns]}

    {

        int i{0};

        for(auto it1 = list.begin(); i<NumberRows ; ++it1, ++i)

        {

            int j{0};

            for(auto it2 = it1->begin(); j<NumberColumns ; ++it2, ++j)

                m_data[get_index(i, j)] = *it2;         // <= change here

        }

    }

  15. Change the multiplication operator, being careful with the indices:

    Matrix3d& Matrix3d::operator*=(const Matrix3d& rhs)

    {

        Matrix3d temp;

        for(int i=0 ; i<NumberRows ; i++)

            for(int j=0 ; j<NumberColumns ; j++)

            {

                temp.m_data[get_index(i, j)] = 0;        // <= change here

                for (int k=0 ; k<NumberRows ; k++)

                    temp.m_data[get_index(i, j)] += m_data[get_index(i, k)]

                                              * rhs.m_data[get_index(k, j)];

                                                         // <= change here

            }

        *this = temp;

        return *this;

    }

  16. With these changes in place, we have fixed all the compiler errors, but now we have a linker error to deal with – the copy constructor that we only declared back in step 11.
  17. In the matrix3d.cpp file add the following definitions:

    Matrix3d::Matrix3d(const Matrix3d& rhs) :

        m_data{new float[NumberRows*NumberColumns]}

    {

        *this = rhs;

    }

    Matrix3d& Matrix3d::operator=(const Matrix3d& rhs)

    {

        for(int i=0 ; i< NumberRows*NumberColumns ; i++)

            m_data[i] = rhs.m_data[i];

        return *this;

    }

  18. The tests will now build and all of them will pass. The next step is to force a move constructor. Locate the createTranslationMatrix() method in matrix3d.cpp and change the return statement as follows:

    return std::move(matrix);

  19. In matrix3d.hpp declare the move constructor.

    Matrix3d(Matrix3d&& rhs);

  20. Rebuild the tests. Now, we get an error related to the move constructor not being present.
  21. Add the implementation of the constructor into matrix3d.cpp and rebuild the tests.

    Matrix3d::Matrix3d(Matrix3d&& rhs)

    {

        //std::cerr << "Matrix3d::Matrix3d(Matrix3d&& rhs)\n";

        std::swap(m_data, rhs.m_data);

    }

  22. Rebuild and run the tests. They all pass again.
  23. Just to confirm that the move constructor is being called, add #include <iostream> to matrix3d.cpp, remove the comment from the output line in the move constructor. and re-run the test. It will report an error after the tests have completed because we sent it to the standard error channel (cerr). After the check, make the line a comment again.

    Note

    Just a quick note about the move constructor – we did not explicitly initialize m_data like we did for the other constructors. This means that it will be initialized as empty and then swapped with the parameter that is passed in, which is a temporary and so it is acceptable for it to not hold an array after the transaction – it removes one allocation and deallocation of memory.

  24. Now let's convert the Point3d class so that it can use heap allocated memory. In the point3d.hpp file, add an #include <memory> line so that we have access to the unique_ptr<> template.
  25. Next, change the type of the declaration for m_data to be like so:

    std::unique_ptr<float[]> m_data;

  26. The compiler now tells us that we have a problem with the insertion operator (<<) in point3d.hpp because we can't use a ranged-for on unique_ptr: Replace the implementation with the following:

    inline std::ostream&

    operator<<(std::ostream& os, const Point3d& pt)

    {

        const char* sep = "[ ";

        for(int i{0} ; i < Point3d::NumberRows ; i++)

        {

            os << sep << pt.m_data[i];

            sep = ", ";

        }

        os << " ]";

        return os;

    }

  27. Open point3d.cpp and modify the default constructors to initialize the unique_ptr and change the initialization loop since a ranged for cannot be used on the unique_ptr:

    Point3d::Point3d() : m_data{new float[NumberRows]}

    {

        for(int i{0} ; i < NumberRows-1 ; i++) {

            m_data[i] = 0;

        }

        m_data[NumberRows-1] = 1;

    }

  28. Modify the other constructor by initializing the unique_ptr:

    Point3d::Point3d(std::initializer_list<float> list)

                : m_data{new float[NumberRows]}

  29. Now all the tests run and pass, like they did previously.
  30. Now, if we run the original application, L3graphics, then the output will be identical to the original, but the implementation uses RAII to allocate and manage the memory that's used for the matrices and points.
Figure 3.52: Activity 1 output after successful conversion to use RAII
Previous PageNext Page
You have been reading a chapter from
Advanced C++
Published in: Oct 2019Publisher: ISBN-13: 9781838821135

Authors (5)

author image
Gazihan Alankus

Gazihan Alankus holds a PhD in computer science from Washington University in St. Louis. Currently, he is an assistant professor at the Izmir University of Economics in Turkey. He teaches and conducts research on game development, mobile application development, and human-computer interaction. He is a Google developer expert in Dart and develops Flutter applications with his students in his company Gbot, which he founded in 2019.
Read more about Gazihan Alankus

author image
Olena Lizina

Olena Lizina is a software developer with 5 years experience in C++. She has practical knowledge of developing systems for monitoring and managing remote computers with a lot of users for an international product company. For the last 4 years, she has been working for international outsourcing companies on automotive projects for well-known automotive concerns. She has been participating in the development of complex and high-performance applications on different projects, such as HMI (Human Machine Interface), navigation, and applications for work with sensors.
Read more about Olena Lizina

author image
Rakesh Mane

Rakesh Mane has over 18 years of experience in the software industry. He has worked with proficient programmers from a variety of regions such as India, the US, and Singapore. He has mostly worked in C++, Python, shell scripting, and database. In his spare time, he likes to listen to music and travel. Also, he likes to play with, experiment with, and break things using software tools and code.
Read more about Rakesh Mane

author image
Vivek Nagarajan

Vivek Nagarajan is a self-taught programmer who started out in the 1980s on 8-bit systems. He has worked on a large number of software projects and has 14 years of professional experience with C++. Aside from this, he has worked on a wide variety of languages and frameworks across the years. He is an amateur powerlifter, DIY enthusiast, and motorcycle racer. He currently works as an independent software consultant.
Read more about Vivek Nagarajan

author image
Brian Price

Brian Price has over 30 years experience working in a variety of languages, projects, and industries, including over 20 years' experience in C++. He was worked on power station simulators, SCADA systems, and medical devices. He is currently crafting software in C++, CMake, and Python for a next-generation medical device. He enjoys solving puzzles and the Euler project in a variety of languages.
Read more about Brian Price