2

设置

我有一个图形库,我试图在其中尽可能多地分解事物,我发现的最简洁的描述方式如下:有一个node仅实现边列表的 vanilla 类型:

class node
{
   public:
      int* edges;
      int edge_count;
};

然后,我希望能够为整个组合添加接口,如下所示:

template <class T>
class node_weight
{
   public:
      T weight;
};

template <class T>
class node_position
{
   public:
      T x;
      T y;
};

等等。然后,实际的图形类进来了,它以实际的节点类型为模板:

template <class node_T>
class graph
{
   protected:
      node_T* nodes;

   public:
      static graph cartesian(int n, int m)
      {
         graph r; 

         r.nodes = new node_T[n * m];

         return r;
      }
};

不同之处在于它命名了构造一些特殊图形的构造函数,比如笛卡尔格。在这种情况下,我希望能够在图中添加一些额外的信息,具体取决于node_T.

实现这一目标的最佳方法是什么?

可能的解决方案

我想到了以下不起眼的解决方案,通过dynamic_cast<>

template <class node_T, class weight_T, class position_T>
class graph
{
   protected:
      node_T* nodes;

   public:
      static graph cartesian(int n, int m)
      {
         graph r;

         r.nodes = new node_T[n * m];

         if (dynamic_cast<node_weight<weight_T>>(r.nodes[0]) != nullptr)
         {
            // do stuff knowing you can add weights
         }

         if (dynamic_cast<node_position<positionT>>(r.nodes[0]) != nullptr)
         {
            // do stuff knowing you can set position
         }

         return r;
      }
};

这将在node_T以下情况下运行:

template <class weight_T, class position_T>
class node_weight_position : 
      public node, public node_weight<weight_T>, public node_position<position_T>
{
    // ...
};

问题

从哲学上讲,这是正确的方法吗?我知道人们不喜欢多重继承,尽管像这样的“接口”应该没问题。

不幸的是,这存在一些问题。至少据我所知,dynamic_cast<>涉及相当多的运行时开销。因此,我遇到了我之前解决的问题:编写需要权重的图形算法,而与实际node_T类是否具有权重无关。这种“接口”方法的解决方案是编写一个函数:

template <class node_T, class weight_T>
inline weight_T get_weight(node_T const & n)
{
   if (dynamic_cast<node_weight<weight_T>>(n) != nullptr)
   {
      return dynamic_cast<node_weight<weight_T>>(n).weight;
   }

   return T(1);
}

但它的问题是它使用运行时信息(dynamic_cast)工作,但原则上我想在编译时决定它,从而使代码更有效率。

如果有一种不同的解决方案可以解决这两个问题,尤其是比我拥有的更清洁、更好的解决方案,我很想听听!

4

2 回答 2

3

类型特征呢?如果你有一个方便的编译器,它已经支持 C++11 的部分内容,std::is_base_of那么<type_traits>标题中就有。

如果你不这样做,就会有具有相同类型特征的提升。

现在,要真正能够使用它,您需要一些元编程:

// in the class...
//  branch on whether the node type has weights
static void set_weights(node_T* nodes, std::true_type){
    // has weights, set them
    // ...
}

static void set_weight(node_T* nodes, std::false_type){
    // doesn't have weights, do nothing
}

// in the function...
typedef std::is_base_of<node_weight<weight_T>, node_T>::type has_weights;
set_weight(nodes, has_weights());

这要归功于一些魔法,它让嵌套的 typedeftype要么基于类型特征,true_type要么false_type基于类型特征是真还是假。我们需要元编程(通过重载分支),因为访问不存在的成员会导致编译器错误,即使访问是在永远不会执行的分支中。

我希望这有任何意义,在 iPod Touch 上输入这个主题的答案非常困难......

于 2011-11-18T01:40:47.043 回答
1

首先,在正确的时间使用时,我非常喜欢多重继承。因此,如果它使您的设计更简单,请使用它。至于摆脱 dynamic_cast<> 并用一个简单的编译时选择替换它。您只需使用重载函数为您进行切换。当您无法对类型做任何有用的事情时,您有一个函数采用 void* ,而一个函数对指定的类型做一些有用的事情。您的代码如下所示。

template <class node_T, class weight_T, class position_T>
class graph
{
protected:
    node_T* nodes;

private:
    static void do_stuff_with_weights(graph& r, void* /*dummy*/)
    {
    }

    static void do_stuff_with_weights(graph& r, node_weight<weight_T>* /*dummy*/)
    {
        // do stuff knowing you can add weights
    }

    static void do_stuff_with_pos(graph& r, void* /*dummy*/)
    {
    }

    static void do_stuff_with_pos(graph& r, node_position<position_T>* /*dummy*/)
    {
            // do stuff knowing you can set position
    }

public:
    static graph cartesian(int n, int m)
    {
        graph r;

        r.nodes = new node_T[n * m];

        do_stuff_with_weights(r, (node_T*) 0);
        do_stuff_with_pos(r, (node_T*) 0);

        return r;
    }
};
于 2011-11-18T01:35:36.737 回答