跳转至内容

更多 C++ 习语/类型生成器

来自维基教科书,开放的书籍,开放的世界

类型生成器

[编辑 | 编辑源代码]
  • 简化创建复杂的基于模板的类型
  • 根据模板参数合成一个或多个新类型
  • 在使用基于策略的类设计时将默认策略本地化

也称为

[编辑 | 编辑源代码]

模板化 typedef 习语

使用基于策略的类设计设计的类模板,通常会生成具有多个类型参数的非常灵活的模板。这种类模板的一个缺点是,在实例化它们时必须提供太多类型参数。在这种情况下,默认模板参数可以提供帮助。但是,当最后一个模板参数(策略类)与默认值不同时,必须指定所有中间模板参数。

例如,考虑在标准 C++ 容器中使用特殊用途分配器的情况。GNU C++ 编译器在命名空间__gnu_cxx中提供了许多特殊用途分配器,作为标准 C++ 库的扩展。以下说明了使用 GNU 的 malloc_allocator 特化 std::map

std::map <std::string, int, less<std::string>, __gnu_cxx::malloc_allocator<std::string>>

使用float而不是int的上述 map 的变体要求再次提及所有不相关的类型参数。

std::map <std::string, float, less<std::string>, __gnu_cxx::malloc_allocator<std::string> >

类型生成器习语用于在这种情况下减少代码膨胀。

解决方案和示例代码

[编辑 | 编辑源代码]

在类型生成器习语中,一组类型定义的公共(不变)部分被收集到一个结构中,该结构的唯一目的是生成另一个类型。例如,考虑下面显示的Directory模板。

template <class Value>
struct Directory
{
  typedef std::map <std::string, Value, std::less<std::string>, 
                    __gnu_cxx::malloc_allocator<std::string> > type;
};

Directory<int>::type    // gives a map of string to integers.
Directory<float>::type  // gives a map of string to floats.

使用额外的间接级别(struct Directory)来捕获不变部分,并留下一个或两个模板参数用于自定义。类型生成器通常会将复杂的类型表达式合并为一个简单的表达式。类型生成器可以通过简单地添加更多 typedef 来用于生成多个类型。

例如,考虑如何将标准 STL 算法应用于映射。

Directory<int>::type age; // This is a map.
transform(age.begin(), age.end(),
          std::ostream_iterator<string>(std::cout, "\n"),
          _Select1st<std::map<std::string, int>::value_type> ());

一个适配器,它将 map 的 value_type(一个 pair)转换为 pair 的第一个元素。_Select1st 在上面的示例中担任适配器的角色。它的类型过于复杂,在多次重复时有大量出错的可能性。相反,类型生成器习语大大简化了适配器类型规范。

template <class Value>
struct Directory
{
  typedef map <string, Value, less<string>, __gnu_cxx::malloc_allocator<std::string> > type;
  typedef _Select1st<typename type::value_type> KeySelector;
  typedef _Select2nd<typename type::value_type> ValueSelector;
};
Directory<int>::type age;    // This is a map.
transform(age.begin(), age.end(),
          std::ostream_iterator<string>(std::cout, "\n"),
          Directory<int>::KeySelector());

最后,类型生成器习语可以用于方便地更改不变类型参数(如果需要)。例如,在整个程序中将malloc_allocator更改为debug_allocator。您可能有时想要更改它的主要原因是在调试时从边界检查或内存泄漏检测工具中获得更多有用的信息。使用类型生成器,只需在一个地方更改,就可以实现这种程序范围内的效果。

C++11 添加了using并试图将其作为typedef的继任者。与typedef不同,using支持模板,消除了附加::type的需要。using也更难搞砸,因为typename从不需要。<type_traits>头文件从 C++14 开始就有_t_v版本的类型特征,所以您可以使用some_type_trait_t<Ts...>来代替typename some_type_trait<Ts...>::type。如果您之前使用旧的习语编写了类,您也可以编写以_t_v结尾的辅助别名模板。

// Old code kept for backwards compatibility
template <class Value>
struct Directory
{
  typedef map <string, Value, less<string>, __gnu_cxx::malloc_allocator<std::string> > type;
  typedef _Select1st<typename type::value_type> KeySelector;
  typedef _Select2nd<typename type::value_type> ValueSelector;
};

// What you should be using instead
template <class Value>
using Directory_t = typename Directory<Value>::type;
template <class Value>
using DirectoryKeySelector = typename Directory<Value>::KeySelector;
template <class Value>
using DirectoryValueSelector = typename Directory<Value>::ValueSelector;

// Code changed to use the new types
Directory_t<int> age; // No need to remember whether typename is necessary
transform(age.begin(), age.end(),
          std::ostream_iterator<string>(std::cout, "\n"),
          DirectoryKeySelector<int>()); // No need to remember whether typename is necessary

已知用途

[编辑 | 编辑源代码]
  • Boost.Iterator 库
  • 头文件<type_traits>
[编辑 | 编辑源代码]

参考资料

[编辑 | 编辑源代码]

[1] 类型生成器

[2] 策略适配器和 Boost 迭代器适配器库 -- David Abrahams 和 Jeremy Siek

[3] 模板 typedef -- Herb Sutter

[4] 新的 C++:typedef 模板 -- Herb Sutter

华夏公益教科书