清理大量使用模板参数

时间:2010-08-06 09:36:01

标签: c++ templates

我在项目A和B使用的日志框架中有一组类。我正在重构框架,因此可以在项目B和C中使用。重构主要包括提供所有模板参数:项目A可能运行在具有差/无STL实现的嵌入式设备上,而B和C只在PC上运行,但B是单线程而C使用多线程。

这很好用,但是在我看来有很多模板参数和一个相当难看的typedef混乱。我需要20行来键入我将要使用的所有类,并且还有很多类采用模板参数,他们不使用自己,但是需要能够键入dede他们使用的另一个类(这不是本身就是一件坏事,但最终一切都开始变得非常复杂了。另一个问题是,当我想向A类添加一些功能并且需要添加容器时,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;
}

这使得它更简单,但它不是同时隐藏了什么消息实际上是什么?假设用户想要提供实现,他不会直接看到该消息必须使用某种字符串类型等。

我想知道的另一种技术,但不记得曾经在somwhere之前看到它让它看起来可疑,只是在一个结构中定义所有内容并将其作为单个模板参数传递给所有内容:

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;
};

这很好,因为它允许在一个单点指定所有内容,并且所有类的typedef都将是类XXX&lt; MyTemplateParameters&gt;,但同样,它给我一种不安的感觉。有这个原因吗?

1 个答案:

答案 0 :(得分:5)

“其他”技术在C ++中非常常见。参数类通常称为“ trait class ”。

这是要走的路(为什么它给你一种不安的感觉?)。它在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;
};

这可以用于使任意类型(特别是模板参数)非const。这实际上是我目前在项目中使用的类型:我有一个接受const和非const类型的类,并提供适当的接口。但是,内部我需要存储一个非const引用。很简单,我只在课堂上使用以下代码:

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可以是CompliantEmbedded。因此,要在符合标准的编译器上使用它,请写:

ThreadedMessageAcceptor<Compliant> x;

该类使用以下元函数:

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

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

您需要记住将它们专门用于您的目标规格,例如:

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之外,元函数可能有任意多个参数(我最后一次突发奇想 - 但它的位置应该是一致的。)

可以肯定的是,这比使用单个特征类时的代码更多,但它具有较低的内聚力,逻辑上将关注点分开并且更容易重用。