我最近看到一些使用宏的代码,如
#define CONTAINS(Class, Name)\
private:\
std::list<Class> m_##Name##s;\
public:\
void add_##Name(const Class& a_##Name) {\
m_##Name##s.push_back(a_##Name);\
}\
int get_##Name(int pos) {\
return m_##Name##s.at(pos);\
}\
// ... more member functions
稍后您可以声明类似
的类class my_class {
CONTAINS(int, integer)
// ...
};
并写
my_class a(...);
a.add_integer(10);
我对这种粘贴宏观风格感到困惑,因为我缺少具体的反驳论据。但除此之外,我接受以下职业选手
add_integer(10)
)现在我正在寻找符合上述所有要点的替代品,并避免使用旧的C宏样式。我的第一个想法是创建一个抽象基类模板
template<typename T>
class list_interface {
private:
std::list<T> m_list;
public:
void add_element(const T& x) {
m_list.push_back(x);
}
// ... more member functions
};
并通过像
这样的继承将它添加到我的班级class my_class : public list_interface<int> {
// ...
};
现在我也可以写了
my_class a;
a.add_element(10);
但我担心以下几点:
add_element(10)
而不是add_integer(10)
)我的问题是:
答案 0 :(得分:2)
怎么样:
#include <vector>
template<typename T>
class Plop
{
std::vector<T> data;
public:
void add(T const& v) {data.push_back(v);}
T get(int pos) {return data.at(pos);} // at() is not valid on lists.
};
class my_class
{
public:
Plop<int> integer;
Plop<float> floater;
};
int main()
{
my_class x;
x.integer.add(5); // similar to x.add_integer(5);
x.integer.get(0); // similar to x.get_integer(0);
}
它符合所有要求:
我的问题是:
旧C宏构造的缺点是什么
如何在没有宏的情况下提供类似的功能
答案 1 :(得分:1)
我的意见
1)Yuck yuck。复杂的宏将阻碍调试。只是宏的视图让我的皮肤爬行。
2)你的继承解决方案看起来很好。如果您确实需要多个不同类型的列表,您可能需要考虑编写更多代码并将列表实例化为成员变量。尝试减少代码行是没有任何好处的。
答案 2 :(得分:1)
有一种方式,以元编程方式,并使用标签。
首先,让我们考虑roll your own
解决方案。
想法是想出这个界面:
class my_class : public vector<Name, std::string>, public vector<Foo, int>
{
};
然后,像这样使用它:
my_class a;
a.add<Name>("Peter");
a.add<Foo>(3);
现在,让我们在封面后面潜水。我们将SFINAE与enable_if结合使用。
template <class Tag, class Type>
class vector
{
template <class T, Return>
struct Enable
{
typedef typename boost::enable_if<
boost::is_same<T,Tag>,
Return
>::type type;
}; // Enable
public:
template <class T>
typename Enable<T,void>::type
add(Type const& i) { m_elements.push_back(i); }
template <class T>
typename Enable<T, Type const&>::type
get(size_t i) const { return m_elements.at(i); }
// You'd better declare a whole lot of other methods if you really want that
// like empty, size and clear at the very least.
// Just use the same construct for the return type.
protected:
vector() : m_elements() {}
vector(vector const& rhs) : m_elements(rhs.m_elements) {}
vector& operator=(vector const& rhs) { m_elements = rhs.m_elements; return *this; }
~vector() {} // Not virtual, because cannot be invoked publicly :)
private:
std::vector<Type> m_elements; // at() is inefficient on lists
};
这是如何运作的?
基本上,当您调用get<Name>
时,编译器有两种选择:
vector<Name,std::string>::get
vector<Foo,int>::get
现在,多亏了enable_if,第二种选择是格式错误(由于Foo
!= Name
而无法使用推断的类型),因此,由于SFINAE,此替代方案已从列表没有任何投诉。
然后,由于只有一个替代方案,因此会被选中。当然,由于这是在编译时完成的,因此实际上没有任何运行时惩罚。
如果你想跳过一些工作(对于这种类型)。您也可以简单地使用更简单的构造:
template <class Tag, class Embedded>
class Embed
{
// redeclares same private and protected interface
public:
template <class T>
typename Enable<T,Embedded &>::type get() { return m_element; }
template <class T>
typename Enable<T,Embedded const&>::type get() const { return m_element; }
private:
Embedded m_element;
};
然后你就这样使用它:
class my_class: public Embed< Names, std::vector<std::string> >,
public Embed<Foo,int>
{
};
my_class a;
std::vector<std::string> const& names = a.get<Names>();
int foo = a.get<Foo>();
a.get<Names>().push_back("Peter");
它更容易,因为您只提供访问者而不必为了转发工作而编写一大堆方法。
现在我们已经做了很多工作,我们应该问自己:这看起来很实用和通用,肯定有图书馆或其他什么?
有&gt;&gt; Boost.Fusion's map:
class my_class
{
public:
template <class Tag>
typename result_of::at_key<map_type, T>::type &
get() { return at_key<T>(m_data); }
template <class Tag>
typename result_of::at_key<map_type, T>::type const&
get() const { return at_key<T>(m_data); }
private:
// First element of the pair: TAG
// Second element of the pair: Actual type of the data
typedef boost::fusion::map <
std::pair<Name, std::vector<std::string> >,
std::pair<Foo, int>
> map_type;
map_type m_data;
};
答案 3 :(得分:1)
宏的主要问题是:它正在解决你真正没有的问题。
它正在创建一个包含列表成员的类,其中列表是直接操作的。 因为我们认为在OO中,成员应该被封装,我们想要使用DRY,我们来到这个构造,而my_class仍然是一个数据类。
如果所有类都要包含列表,请将其转换为结构并将列表保持公开。 这样你就有了干净的意图,并且可以通过STL方式访问列表。 如果类需要控制列表,那么你不应该公开列表,而宏的用处很少。
所以我的代码将是(未编译):
struct my_class {
std::list<int> integers;
std::list<std::string> names;
// ...
};
int main()
{
my_class lists;
lists.integers.push_back(5);
size_t size_names = lists.names.size();
}
临:
缺点:
答案 4 :(得分:0)
对于多个列表,您可以尝试使用typedef进行多重继承。类似的东西:
class my_class : public list_interface<int>, public list_interface<float> {
public:
typedef list_interface<int> Ints;
typedef list_interface<float> Floats;
//...
};
并使用如:
my_class a;
a.Ints::add_element(10);
a.Floats::add_element(10.0f);