这种宏观构造的下行和可能的替代方案

时间:2009-11-06 09:21:16

标签: c++ interface macros class-design

我最近看到一些使用宏的代码,如

#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);

我对这种粘贴宏观风格感到困惑,因为我缺少具体的反驳论据。但除此之外,我接受以下职业选手

  1. 您可以轻松地将任意类型的列表界面添加到您的班级
  2. 你避免经常重复的代码
  3. 您有一个易于使用的界面(如add_integer(10)
  4. 现在我正在寻找符合上述所有要点的替代品,并避免使用旧的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);
    

    但我担心以下几点:

    1. 您只能向班级添加一个列表
    2. 您公开从没有虚拟析构函数的类继承
    3. 我没有达到专业人士的第三点(add_element(10)而不是add_integer(10)
    4. 我的问题是:

      1. 旧C宏构造的缺点是什么
      2. 如何在没有宏的情况下提供类似的功能

5 个答案:

答案 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);
}

它符合所有要求:

  1. 您可以轻松地将任意类型的列表界面添加到您的班级
  2. 你避免经常重复的代码
  3. 你有一个易于使用的界面(如add_integer(10))
  4. 我的问题是:

    1. 旧C宏构造的缺点是什么

      • ick factor。
      • 调试。
      • 可以将个人列表传递给其他功能或方法。
    2. 如何在没有宏的情况下提供类似的功能

      • 见上文。

答案 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);

现在,让我们在封面后面潜水。我们将SFINAEenable_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();
}

临:

  1. 轻松添加列表
  2. 没有代码重复
  3. 一致(STL)界面
  4. 直接的
  5. 没有宏的
  6. 缺点:

    1. 没有数据封装,如果这是一个要求

答案 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);