在进行大量类似的方法调用时避免定义宏
Avoiding define macro when making a lot of similar method calls
下面的代码使用了 #define
宏。我想避免这种情况,但我没有看到使用 C++ 功能 and 的方法更具可读性。该代码用于测试将 PoorLegacyClass
的两个实例合并为一个的 class ClassUnderTest
。这个 PoorLegacyClass
有很多 getter 和 setter,出于测试目的,我需要调用很多 setter(或者我需要调用吗?)。
总之,不用写
a.setValueX(20);
b.setValueX(30);
我想写点像
set(a, b, ValueX, 20, 30);
// or
set<ValueX>(a, 20, b, 30);
// or even
set(a, b, &PoorLegacyClass::setValueX, 20, 30);
这是与我目前使用的代码类似的代码:
#include <boost/test/unit_test.hpp>
#include "ClassUnderTest.h"
BOOST_AUTO_TEST_SUITE(Test_ClassUnderTest);
#define SETTER_A_B(fieldname, valueA, valueB)\
a.set##fieldname(valueA);\
b.set##fieldname(valueB);
struct TestContext
{
PoorLegacyClass a, b;
};
BOOST_FIXTURE_TEST_CASE(ClassUnderTest_meaningful_description1, TestContext)
{
SETTER_A_B(ValueX, 20, 30);
auto result = ClassUnderTest().merge(a, b);
BOOST_TEST(20 == result.getValueX());
}
BOOST_FIXTURE_TEST_CASE(ClassUnderTest_meaningful_description2, TestContext)
{
SETTER_A_B(ValueY, 21, 37);
auto result = ClassUnderTest().merge(a, b);
BOOST_TEST(21 + 37 == result.getValueY());
}
BOOST_FIXTURE_TEST_CASE(ClassUnderTest_meaningful_description3, TestContext)
{
SETTER_A_B(ValueZ, 12, 83);
auto result = ClassUnderTest().merge(a, b);
BOOST_TEST(12 + 83 == result.getValueZ());
}
BOOST_FIXTURE_TEST_CASE(ClassUnderTest_meaningful_description4, TestContext)
{
SETTER_A_B(ValueY, 212, 37);
SETTER_A_B(ValueX, 20, 30);
auto result = ClassUnderTest().merge(a, b);
BOOST_TEST(a.getValueY() == result.getValueY());
}
// more test cases that are similar to those above
BOOST_AUTO_TEST_SUITE_END();
虽然我将来会重构 PoorLegacyClass
这不是这个问题的主题。不过,我想知道的是如何避免使用 #define
宏。
你可以试试这个:
- 使用
BOOST_FUSION_ADAPT_STRUCT
对你的结构进行融合序列。
- 然后使用
fusion::at_c<N>
访问第N个元素并设置它的值
- 在上面放置一个自由函数来获取序列并使用
at_c<N>
适当地访问成员。
应该避免使用宏..
既然您愿意接受语法 set(a, b, &PoorLegacyClass::setValueX, 20, 30);
,那么实施这样的 set
应该很容易:
template <class T>
void set(PoorLegacyClass &a, PoorLegacyClass &b, void (PoorLegacyClass::*setter)(T), T valForA, T valForB)
{
(a.*setter)(valForA);
(b.*setter)(valForB);
}
您还可以使 valFor
参数在非推导上下文中使用 T
,这样 T
只能从 setter 推导隐式转换发生在值上:
template <class T>
struct NonDeduced { using type = T; }
template <class T>
void set(PoorLegacyClass &a, PoorLegacyClass &b, void (PoorLegacyClass::*setter)(T), typename NonDeduced<T>::type valForA, typename NonDeduced<T>::type valForB)
set(a, b, ValueX, 20, 30)
或 set<ValueX>(a, 20, b, 30)
都需要某种预处理才能将 "set"
和 "ValueX"
连接成一个新术语。
我个人认为一些预处理器比大量样板好,但正如@Angew 所说,您可以轻松选择第三种选择:
set(a, b, &PoorLegacyClass::setValueX, 20, 30);
我唯一想补充的是,您可以使用可变参数模板使它适用于任意数量的对象:
template<class T> void set(void (PoorLegacyClass::*setter)(T))
{}
template<class T, typename... Args> void set(void (PoorLegacyClass::*setter)(T),
PoorLegacyClass& obj, T val, Args&&... args)
{
(obj.*setter)(val);
set(setter, std::forward<Args>(args)...);
}
在这种情况下,调用语法会略有变化:
set(&PoorLegacyClass::setValueX, a, 20, b, 30);
下面的代码使用了 #define
宏。我想避免这种情况,但我没有看到使用 C++ 功能 and 的方法更具可读性。该代码用于测试将 PoorLegacyClass
的两个实例合并为一个的 class ClassUnderTest
。这个 PoorLegacyClass
有很多 getter 和 setter,出于测试目的,我需要调用很多 setter(或者我需要调用吗?)。
总之,不用写
a.setValueX(20);
b.setValueX(30);
我想写点像
set(a, b, ValueX, 20, 30);
// or
set<ValueX>(a, 20, b, 30);
// or even
set(a, b, &PoorLegacyClass::setValueX, 20, 30);
这是与我目前使用的代码类似的代码:
#include <boost/test/unit_test.hpp>
#include "ClassUnderTest.h"
BOOST_AUTO_TEST_SUITE(Test_ClassUnderTest);
#define SETTER_A_B(fieldname, valueA, valueB)\
a.set##fieldname(valueA);\
b.set##fieldname(valueB);
struct TestContext
{
PoorLegacyClass a, b;
};
BOOST_FIXTURE_TEST_CASE(ClassUnderTest_meaningful_description1, TestContext)
{
SETTER_A_B(ValueX, 20, 30);
auto result = ClassUnderTest().merge(a, b);
BOOST_TEST(20 == result.getValueX());
}
BOOST_FIXTURE_TEST_CASE(ClassUnderTest_meaningful_description2, TestContext)
{
SETTER_A_B(ValueY, 21, 37);
auto result = ClassUnderTest().merge(a, b);
BOOST_TEST(21 + 37 == result.getValueY());
}
BOOST_FIXTURE_TEST_CASE(ClassUnderTest_meaningful_description3, TestContext)
{
SETTER_A_B(ValueZ, 12, 83);
auto result = ClassUnderTest().merge(a, b);
BOOST_TEST(12 + 83 == result.getValueZ());
}
BOOST_FIXTURE_TEST_CASE(ClassUnderTest_meaningful_description4, TestContext)
{
SETTER_A_B(ValueY, 212, 37);
SETTER_A_B(ValueX, 20, 30);
auto result = ClassUnderTest().merge(a, b);
BOOST_TEST(a.getValueY() == result.getValueY());
}
// more test cases that are similar to those above
BOOST_AUTO_TEST_SUITE_END();
虽然我将来会重构 PoorLegacyClass
这不是这个问题的主题。不过,我想知道的是如何避免使用 #define
宏。
你可以试试这个:
- 使用
BOOST_FUSION_ADAPT_STRUCT
对你的结构进行融合序列。 - 然后使用
fusion::at_c<N>
访问第N个元素并设置它的值 - 在上面放置一个自由函数来获取序列并使用
at_c<N>
适当地访问成员。
应该避免使用宏..
既然您愿意接受语法 set(a, b, &PoorLegacyClass::setValueX, 20, 30);
,那么实施这样的 set
应该很容易:
template <class T>
void set(PoorLegacyClass &a, PoorLegacyClass &b, void (PoorLegacyClass::*setter)(T), T valForA, T valForB)
{
(a.*setter)(valForA);
(b.*setter)(valForB);
}
您还可以使 valFor
参数在非推导上下文中使用 T
,这样 T
只能从 setter 推导隐式转换发生在值上:
template <class T>
struct NonDeduced { using type = T; }
template <class T>
void set(PoorLegacyClass &a, PoorLegacyClass &b, void (PoorLegacyClass::*setter)(T), typename NonDeduced<T>::type valForA, typename NonDeduced<T>::type valForB)
set(a, b, ValueX, 20, 30)
或 set<ValueX>(a, 20, b, 30)
都需要某种预处理才能将 "set"
和 "ValueX"
连接成一个新术语。
我个人认为一些预处理器比大量样板好,但正如@Angew 所说,您可以轻松选择第三种选择:
set(a, b, &PoorLegacyClass::setValueX, 20, 30);
我唯一想补充的是,您可以使用可变参数模板使它适用于任意数量的对象:
template<class T> void set(void (PoorLegacyClass::*setter)(T))
{}
template<class T, typename... Args> void set(void (PoorLegacyClass::*setter)(T),
PoorLegacyClass& obj, T val, Args&&... args)
{
(obj.*setter)(val);
set(setter, std::forward<Args>(args)...);
}
在这种情况下,调用语法会略有变化:
set(&PoorLegacyClass::setValueX, a, 20, b, 30);