【问题标题】:Templates, interfaces (multiple inheritance) and static functions (named constructors)模板、接口(多重继承)和静态函数(命名构造函数)
【发布时间】:2011-11-18 01:03:46
【问题描述】:

设置

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

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&lt;&gt;

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&lt;&gt; 涉及相当多的运行时开销。因此,我遇到了我之前解决的问题:编写需要权重的图形算法,而与实际的 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) 工作,但原则上我想在编译时决定它,从而使代码更有效率。

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

【问题讨论】:

  • 天哪。按值返回的构造函数?原始指针?具有指针成员且没有复制构造函数的类?我认为您可能应该从更简单的东西开始并逐渐建立它......
  • 请评论手头的问题,而不是演示文稿中的选择:)。我写它是为了便于理解;实际上有一个 std::vector 而不是指针,事情不是公开的,等等。
  • 关于 CRTP: 没有帮助,dynamic_cast 用于检查是否实际实现了特定接口。问题是node_T 类型在编译时为编译器所知,因此应该有一种方法可以使用该知识并根据实际类型改变函数的行为。
  • @fledgeling:有几个不连贯的小事情至少会引起人们的注意,所以很难简明扼要地说除了“更仔细地设计它”之外应该做什么。也许如果你把它分解成更小的部分,它们可以一一简洁地回答。不管怎样,你似乎已经得到了你想要的东西,所以祝你好运!

标签: c++ templates multiple-inheritance named-constructor


【解决方案1】:

类型特征呢?如果您手头有一个已经支持部分 C++11 的编译器,那么在 &lt;type_traits&gt; 标头中有 std::is_base_of

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

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

// 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());

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

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

【讨论】:

  • 类型特征,似乎是最干净的方法。
  • 很好的答案! 20 小时后,当我的选票重新填满时,我会投票。
【解决方案2】:

首先,我非常喜欢在正确的时间使用多重继承。因此,如果它使您的设计更简单,请使用它。至于摆脱 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;
    }
};

【讨论】:

  • 比我的版本简单得多,而且不使用类型特征...+1
  • 这提醒了我,为什么要使用虚拟参数?只需传递nodes 自己,因为它是从派生到基的简单向上转换。
  • 谢谢@Xeo。如果不清楚 fedgling-cxx-user,Xeo 和我的答案都使用相同的技术,即可以使用重载函数有点像 switch/if 语句,其中选择是根据类型完成的参数而不是整数索引/条件。这是一种非常方便的技术,在实现条件行为的通用编程中经常出现。
  • @Xeo,这是一种习惯。我倾向于传递一个虚拟值来指示该参数用于选择正确的函数,而不是该函数的正确参数。但是是的 r.nodes[0] 本来是相当合理的。
  • @fledgling Cxx 用户:这里使用的技术的解释可以在this wonderful lecture by Stephen T Lavavej from Channel9找到。
猜你喜欢
  • 2014-06-26
  • 1970-01-01
  • 1970-01-01
  • 2013-05-21
  • 2019-12-14
  • 2015-08-06
  • 2012-04-24
  • 2011-07-06
  • 1970-01-01
相关资源
最近更新 更多