Шаблоны, интерфейсы (множественное наследование) и статические функции (именованные конструкторы) - PullRequest
2 голосов
/ 18 ноября 2011

Настройка

У меня есть библиотека графов, в которой я пытаюсь максимально разложить вещи, и я нашел самый простой способ описать это следующим образом: существует ванильный тип 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<>:

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), но в принципе я хотел бы решить это во время компиляции и, таким образом, сделать код более эффективным.

Если есть другое решение, которое решило бы обе проблемы, особенно более чистое и лучшее, чем у меня, я хотел бы услышать об этом!

Ответы [ 2 ]

3 голосов
/ 18 ноября 2011

А как насчет черт типа?Если у вас есть удобный компилятор, который уже поддерживает части C ++ 11, в заголовке <type_traits> есть 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_type, либо false_type в зависимости от того, является ли черта типа истинной или ложной.Нам нужно метапрограммирование (ветвление через перегрузки), потому что доступ к членам, которых там нет, приведет к ошибке компиляции, даже если доступ был в ветке, которая никогда не будет выполнена.

Я надеюсь, что это делаетв любом случае, довольно сложно набрать ответ на эту тему на iPod Touch ...

1 голос
/ 18 ноября 2011

Прежде всего, я большой поклонник множественного наследования при использовании в нужное время.Так что, если это делает ваш дизайн проще, используйте его.Что касается избавления от 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;
    }
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...