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 4 - Separation of Concerns - Software Architecture, Functions, Variadic Templates

Activity 1: Implement a multicast event handler

  1. Load the prepared project from the Lesson4/Activity01 folder and configure the Current Builder for the project to be CMake Build (Portable). Build the project, configure the launcher and run the unit tests (which fail the one dummy test). Recommend that the name used for the tests runner is L4delegateTests.
  2. In delegateTests.cpp, replace the failing dummy test with the following test:

    TEST_F(DelegateTest, BasicDelegate)

    {

        Delegate delegate;

        ASSERT_NO_THROW(delegate.Notify(42));

    }

  3. This now fails to build, so we need to add a new method to Delegate. As this will evolve into a template, we will do all of this development in the header file. In delegate.hpp, and the following definition:

    class Delegate

    {

    public:

        Delegate() = default;

        void Notify(int value) const

        {

        }

    };

    The test now runs and passes.

  4. Add the following line to the existing test:

    ASSERT_NO_THROW(delegate(22));

  5. Again, the build fails, so we update the Delegate definition as follows (we could have had Notify call operator(), but this is easier to read):

    void operator()(int value)

    {

        Notify(value);

    }

    The test again runs and passes.

  6. Before we add the next test, we are going to add some infrastructure to help us develop our tests. The easiest thing to do with handlers is have them write to std::cout, and to be able to verify that they were called, we need to capture the output. To do this, re-route the standard output stream to a different buffer by changing the DelegateTest class as follows:

    class DelegateTest : public ::testing::Test

    {

    public:

        void SetUp() override;

        void TearDown() override;

        std::stringstream m_buffer;

        // Save cout's buffer here

        std::streambuf *m_savedBuf{};

    };

    void DelegateTest::SetUp()

    {

        // Save the cout buffer

        m_savedBuf = std::cout.rdbuf();

        // Redirect cout to our buffer

        std::cout.rdbuf(m_buffer.rdbuf());

    }

    void DelegateTest::TearDown()

    {

        // Restore cout buffer to original

        std::cout.rdbuf(m_savedBuf);

    }

  7. Also add the include statements for <iostream>, <sstream> and <string> to the top of the file.
  8. With this support framework in place, add the following test:

    TEST_F(DelegateTest, SingleCallback)

    {

        Delegate delegate;

        delegate += [] (int value) { std::cout << "value = " << value; };

        delegate.Notify(42);

        std::string result = m_buffer.str();

        ASSERT_STREQ("value = 42", result.c_str());

    }

  9. To make the tests build and run again, add the following code in the delegate.h class:

    Delegate& operator+=(const std::function<void(int)>& delegate)

    {

        m_delegate = delegate;

        return *this;

    }

    Along with the following code:

    private:

        std::function<void(int)> m_delegate;

    The tests now build, but our new test fails.

  10. Update the Notify() method to be:

    void Notify(int value) const

    {

        m_delegate(value);

    }

  11. The tests now build and our new test passes, but the original test now fails. The call to the delegate is throwing an exception, so we need to check that the delegate is not empty before calling it. Write the following code to do this:

    void Notify(int value) const

    {

        if(m_delegate)

            m_delegate(value);

    }

    All the tests now run and pass.

  12. We now need to add multicast support to the Delegate class. Add the new test:

    TEST_F(DelegateTest, DualCallbacks)

    {

        Delegate delegate;

        delegate += [] (int value) { std::cout << "1: = " << value << "\n"; };

        delegate += [] (int value) { std::cout << "2: = " << value << "\n"; };

        delegate.Notify(12);

        std::string result = m_buffer.str();

        ASSERT_STREQ("1: = 12\n2: = 12\n", result.c_str());

    }

  13. Of course, this test now fails because the operator+=() only assigns to the member variable. We need to add a list to store our delegates. We choose vector so we can add to the end of the list as we want to call the delegates in the order that they are added. Add #include <vector> to the top of delegate.hpp and update Delegate replace m_delegate with m_delegates vector of the callbacks:

    class Delegate

    {

    public:

        Delegate() = default;

        Delegate& operator+=(const std::function<void(int)>& delegate)

        {

            m_delegates.push_back(delegate);

            return *this;

        }

        void Notify(int value) const

        {

            for(auto& delegate : m_delegates)

            {

                delegate(value);

            }

        }

        void operator()(int value)

        {

            Notify(value);

        }

    private:

        std::vector<std::function<void(int)>> m_delegates;

    };

    The tests all run and pass again.

  14. We now have the basic multicast delegate class implemented. We now need to convert it to a template- based class. Update the existing tests, by changing all of the declarations of Delegate to Delegate<int> in the three tests.
  15. Now update the Delegate class by adding template<class Arg> before the class to convert it to a template, and substituting the four occurrences of int with Arg:

    template<class Arg>

    class Delegate

    {

    public:

        Delegate() = default;

        Delegate& operator+=(const std::function<void(Arg)>& delegate)

        {

            m_delegates.push_back(delegate);

            return *this;

        }

        void Notify(Arg value) const

        {

            for(auto& delegate : m_delegates)

            {

                delegate(value);

            }

        }

        void operator()(Arg value)

        {

            Notify(value);

        }

    private:

        std::vector<std::function<void(Arg)>> m_delegates;

    };

  16. All the tests now run and pass as previously, so it stills works for int arguments for the handlers.
  17. Add the following test and re-run the tests to confirm that the template conversion is correct:

    TEST_F(DelegateTest, DualCallbacksString)

    {

        Delegate<std::string&> delegate;

        delegate += [] (std::string value) { std::cout << "1: = " << value << "\n"; };

        delegate += [] (std::string value) { std::cout << "2: = " << value << "\n"; };

        std::string hi{"hi"};

        delegate.Notify(hi);

        std::string result = m_buffer.str();

        ASSERT_STREQ("1: = hi\n2: = hi\n", result.c_str());

    }

  18. Now it operates as a template that takes one argument. We need to convert it into a variadic template that takes zero or more arguments. Using the information from the last topic, update the template to the following:

    template<typename... ArgTypes>

    class Delegate

    {

    public:

        Delegate() = default;

        Delegate& operator+=(const std::function<void(ArgTypes...)>& delegate)

        {

            m_delegates.push_back(delegate);

            return *this;

        }

        void Notify(ArgTypes&&... args) const

        {

            for(auto& delegate : m_delegates)

            {

                delegate(std::forward<ArgTypes>(args)...);

            }

        }

        void operator()(ArgTypes&&... args)

        {

            Notify(std::forward<ArgTypes>(args)...);

        }

    private:

        std::vector<std::function<void(ArgTypes...)>> m_delegates;

    };

    The tests should still run and pass.

  19. Add two more tests – zero argument test, and a mutliple argument test:

    TEST_F(DelegateTest, DualCallbacksNoArgs)

    {

        Delegate delegate;

        delegate += [] () { std::cout << "CB1\n"; };

        delegate += [] () { std::cout << "CB2\n"; };

        delegate.Notify();

        std::string result = m_buffer.str();

        ASSERT_STREQ("CB1\nCB2\n", result.c_str());

    }

    TEST_F(DelegateTest, DualCallbacksStringAndInt)

    {

        Delegate<std::string&, int> delegate;

        delegate += [] (std::string& value, int i) {

                std::cout << "1: = " << value << "," << i << "\n"; };

        delegate += [] (std::string& value, int i) {

            std::cout << "2: = " << value << "," << i << "\n"; };

        std::string hi{"hi"};

        delegate.Notify(hi, 52);

        std::string result = m_buffer.str();

        ASSERT_STREQ("1: = hi,52\n2: = hi,52\n", result.c_str());

    }

    All the tests run and pass showing that we have now implemented the desired Delegate class.

  20. Now, change the Run configuration to execute the program L4delegate. Open the main.cpp file in the editor and change the definition at the top of the file to the following and run the program:

    #define ACTIVITY_STEP 27

    We get the following output:

Figure 4.35: Output from the successful implementation of Delegate

In this activity, we started by implementing a class that provides the basic single delegate functionality and then added multicast capability. With that implemented, and unit tests in place, we were quickly able to convert to a template with one argument and then to a variadic template version. Depending on the functionality that you are developing, the approach of the specific implementation transitioning to a general form and then to an even more general form is the correct one. Development of variadic templates is not always obvious.

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