-
Book Overview & Buying
-
Table Of Contents
Advanced C++ [Instructor Edition]
By :
TEST_F(DelegateTest, BasicDelegate)
{
Delegate delegate;
ASSERT_NO_THROW(delegate.Notify(42));
}
class Delegate
{
public:
Delegate() = default;
void Notify(int value) const
{
}
};
The test now runs and passes.
ASSERT_NO_THROW(delegate(22));
void operator()(int value)
{
Notify(value);
}
The test again runs and passes.
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);
}
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());
}
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.
void Notify(int value) const
{
m_delegate(value);
}
void Notify(int value) const
{
if(m_delegate)
m_delegate(value);
}
All the tests now run and pass.
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());
}
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.
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;
};
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());
}
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.
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.
#define ACTIVITY_STEP 27
We get the following output:
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.
Change the font size
Change margin width
Change background colour