очистка интенсивного использования параметров шаблона - PullRequest
3 голосов
/ 06 августа 2010

У меня есть набор классов в каркасе журналирования, который используется проектами A и B. Я реорганизую каркас, чтобы его можно было использовать в проектах B и C. Рефакторинг в основном состоит из предоставления всех параметров шаблона: проект A может выполнятьсяна встроенном устройстве с плохой реализацией STL или без него, в то время как B и C просто работают на ПК, но B однопоточен, а C использует многопоточность.

Это работает хорошо, но в результате мне кажется, что это ужасномножество шаблонных параметров и довольно безобразный беспорядок в typedef.Мне нужно около 20 строк для определения типа всех классов, которые я собираюсь использовать, и есть также много классов, принимающих параметр шаблона, который они сами не используют, но он необходим для того, чтобы иметь возможность типизировать другой класс, который они используют (который не являетсяСамо по себе это плохо, но в итоге все начинает очень сложно).Другая проблема заключается в том, что когда я хочу добавить некоторые функциональные возможности в класс A, и это требует добавления контейнера, классу A требуется дополнительный параметр шаблона.В результате всем остальным классам, которые видят / используют класс А, внезапно также нужен этот дополнительный параметр, приводящий к эффекту домино.

Слегка преувеличенный пример:

template< class string, class map, class mutex >
class MessageDestination
{
  typedef Message< string, map > message_type;
  virtual void Eat( const message_type& ) = 0;
}

template< class string, class map, class stream >
class MessageFormatter
{
  typedef Message< string, map > message_type;
  virtual void Format( const message_type&, stream& ) = 0;
}

template< class string, class map, class containerA,
          template< class, class > containerB, template< class, class > class queue, class allocator >
class ThreadedMessageAcceptor
{
  typedef Message< string, map > message_type;
  typedef MessageDestination< string, map > destination_type;
  typedef containerB< destination_type, allocator > destinations_type;
  typedef queue< message_type, allocator > messages_type;
};

Я могу придумать некоторые методы дляочистить это, но мне трудно решить, какую или какую комбинацию использовать.StackOverFlow, ваша помощь будет оценена по достоинству!

Вот первое решение, о котором я подумал, объединяя параметры в тип, который они в конечном итоге сформируют:

template< class message, class mutex >
class MessageDestination
{
  virtual void Eat( const message& ) = 0;
}

Это упрощает, но неразве это не скрывает, какое сообщение на самом деле?Предположим, что пользователь хочет предоставить реализацию, он непосредственно не видит, что в сообщении должен использоваться определенный тип строки и т. Д.

Еще один метод, о котором я думаю, но не могу вспомнить, что когда-либо видел ранее, что делает его подозрительным,просто определяет все в одной структуре и передает это как единый параметр шаблона всем:

struct MyTemplateParameters
{
  typedef std::string string;
  typedef std::map map;
  typedef std::queue queue;
  typedef LightMutex mutex;
  template< class A, class B >
  struct DefineContainerB
  {
    typedef containerB< A, B >::type;
  }
  //....
};

template< class parameters >
class MessageDestination
{
  typedef Message< parameters > message_type;
  virtual void Eat( const message_type& ) = 0;
};

template< class parameters >
class ThreadedMessageAcceptor
{
  typedef Message< parameters > message_type;
  typedef MessageDestination< parameters > destination_type;
  typedef parameters::DefineContainerB< destination_type, parameters::allocator >::type destinations_type;
};

Это хорошо, так как позволяет задавать все в одной точке, и typedefs для всех классов будет классомXXX , но опять же, это вызывает у меня неприятное чувство.Есть ли причина для этого?

1 Ответ

5 голосов
/ 06 августа 2010

Техника «другое» очень распространена в C ++. Класс параметров обычно называется « класс черты ».

Это путь (почему он вызывает у вас чувство беспокойства?). Он широко используется в библиотеках Boost и других библиотеках C ++. Даже стандартная библиотека использует его, например, в классе std::basic_string.


Не менее устоявшейся альтернативой являются метафункции. По своей сути метафункция - это «функция», которая работает с типами, а не с объектами. Поэтому, когда функция принимает аргументы-значения и возвращает значение, метафункции принимают аргументы шаблона и «возвращают» тип:

template <typename T>
struct identity {
    typedef T type;
};

Метафункция используется («вызывается») как определение обычного типа.

typedef identity<int>::type mytype; // or
identity<int>::type x;

Не очень полезно в этом случае. Но рассмотрим следующую общую метафункцию:

template <typename T>
struct remove_const {
    typedef T type;
};

template <typename T>
struct remove_const<T const> {
    typedef T type;
};

Это можно использовать, чтобы сделать произвольный тип (в частности, аргумент шаблона) неконстантным. На самом деле это тип, который я сейчас использую в проекте: у меня есть класс, который принимает как константные, так и неконстантные типы и предлагает соответствующие интерфейсы. Однако внутренне Мне нужно хранить неконстантную ссылку. Просто я просто использую следующий код в своем классе:

typename remove_const<T>::type& _reference;

(typename требуется, потому что T является аргументом шаблона, и это делает remove_const<T>::type зависимый тип . В приведенном выше примере кода на самом деле пропущено довольно много обязательных typename s - он не будет компилироваться на нескольких современных компиляторах!)

Теперь, как применить это к вашей проблеме?

Создайте два пустых типа маркера , которые указывают, используются ли ваши типы на встроенном устройстве или на совместимом компиляторе:

struct Embedded { };
struct Compliant { };

Теперь вы можете определять свои классы в терминах этих, например ::

template<typename Spec>
class ThreadedMessageAcceptor
{
    typedef Message< Spec > message_type;
    typedef MessageDestination< Spec > destination_type;
    typedef typename Allocator< destination_type, Spec >::type allocator_type;
    typedef typename ContainerB< destination_type, allocator_type, Spec >::type destinations_type;
};

Здесь Spec будет либо Compliant, либо Embedded. Чтобы использовать его на совместимом со стандартами компиляторе, напишите:

ThreadedMessageAcceptor<Compliant> x;

В классе используются следующие метафункции:

template <typename T, typename Spec>
struct Allocator { };

template <typename T, typename Alloc, typename Spec>
struct ContainerB { };

Вы должны помнить, чтобы специализировать их в соответствии с вашими целевыми характеристиками, например ::1010 *

template <typename T>
struct Allocator<T, Compliant> {
   typedef std::allocator<T> type;
};

template <typename T, typename Alloc>
struct ContainerB<T, Alloc, Compliant> {
    typedef std::vector<T, Alloc> type;
};

Это уже показывает, что метафункция может иметь произвольно много аргументов, кроме Spec (который я поставил последним по своей прихоти - но его размещение должно быть последовательным).

Конечно, это больше кода, чем при использовании одного класса признаков, но он имеет меньшую сплоченность, логически разделяет проблемы и его легче использовать повторно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...