特质和政策有什么区别?

我有一个类,我试图configuration的行为。

template<int ModeT, bool IsAsync, bool IsReentrant> ServerTraits; 

然后,我有我的服务器对象本身:

 template<typename TraitsT> class Server {...}; 

我的问题是我的用法上面是我的命名错误? 我的模板参数实际上是一个政策,而不是一个特点?

什么时候是一个模板化的论点是一个特质还是一个政策?

政策

策略是通常通过inheritance将行为注入父类的类(或类模板)。 通过将父接口分解为正交(独立)维度,策略类形成更复杂接口的构build块。 经常见到的模式是提供策略作为用户可定义的模板(或模板模板)参数与库提供的默认值。 标准库的一个例子是Allocators,它是所有STL容器的策略模板参数

 template<class T, class Allocator = std::allocator<T>> class vector; 

在这里, Allocator模板参数(它本身也是一个类模板!)将内存分配和取消分配策略注入到父类std::vector 。 如果用户不提供分配器,则使用默认的std::allocator<T>

在基于模板的多态中,典型的策略类的接口要求是隐式的和语义的 (基于有效expression式),而不是基于虚拟成员函数定义的显式和句法。

请注意,最近的无序关联容器有多个策略。 除了通常的Allocator模板参数之外,他们还采用默认为std::hash<Key>函数对象的Hash策略。 这允许无序容器的用户沿着多个正交维度(存储器分配和哈希)来configuration它们。

性状

特征是类模板来从genericstypes中提取属性 。 有两种性状:单值性状和多值性状。 单值特征的例子是来自头<type_traits>

 template< class T > struct is_integral { static const bool value /* = true if T is integral, false otherwise */; typedef std::integral_constant<bool, value> type; }; 

单值性状经常用于模板元编程,而SFINAE则根据types条件重载函数模板。

多值traits的例子分别是头<iterator><memory>的iterator_traits和allocator_traits。 由于特征是类模板,所以它们可以是专用的。 下面是针对T*iterator_traits的专业化示例

 template<T> struct iterator_traits<T*> { using difference_type = std::ptrdiff_t; using value_type = T; using pointer = T*; using reference = T&; using iterator_category = std::random_access_iterator_tag; }; 

expression式std::iterator_traits<T>::value_type使得即使对于原始指针(因为原始指针没有成员value_type ),也可以为完整的迭代器类创buildgenerics代码。

政策和特点之间的相互作用

在编写自己的通用库时,重要的是要考虑用户可以专门化你自己的类模板的方式。 但是,必须小心谨慎,不要让用户通过使用特质专业化来注入而不是提取行为而成为“ 一义定律”的受害者。 用安德烈亚历山德雷斯库的这个旧post来解释一下

根本的问题是,没有看到特定版本的特征的代码仍然会编译,可能会链接,有时甚至可能会运行。 这是因为在没有明确的专业化的情况下,非专业化的模板将会起作用,可能会实施一种适用于您的特殊情况的通用行为。 因此,如果不是应用程序中的所有代码都看到特征的相同定义,则ODR被违反。

C ++ 11 std::allocator_traits通过强制所有的STL容器只能通过std::allocator_traits<Allocator>从他们的Allocator策略中提取属性来避免这些缺陷。 如果用户select不要或忘记提供一些必需的政策成员,特质类可以介入并为这些缺less的成员提供缺省值。 因为allocator_traits本身不能被专门化,所以用户总是必须传递一个完全定义的分配器策略来定制它们的容器内存分配,并且不会发生静默的ODR违例。

请注意,作为一个库编写器,仍然可以专门化traits类模板(就像STL在iterator_traits<T*>所做的那样),但是最好将所有用户定义的特化通过策略类传递给多值特征提取专门的行为(就像STL在allocator_traits<A>所做的那样)。

更新 :特征类的用户定义特化的ODR问题主要发生在使用特征作为全局类模板时,并且不能保证所有未来用户都将看到所有其他用户定义的特化。 策略是本地模板参数 ,包含所有相关的定义,允许用户定义它们而不干扰其他代码。 只包含types和常量的局部模板参数 – 但是没有行为函数 – 可能仍然被称为“特征”,但是对于像std::iterator_traitsstd::allocator_traits这样的其他代码是不可见的。

我想你会在Andrei Alexandrescu的书中为你的问题find最好的答案。 在这里,我将尽力简短地介绍一下。 希望这会有所帮助。


traits类是类,它通常是为了将types与其他types或常量值相关联的元函数,以提供这些types的特征。 换句话说,它是模型types属性的一种方法。 该机制通常利用模板和模板专门化来定义关联:

 template<typename T> struct my_trait { typedef T& reference_type; static const bool isReference = false; // ... (possibly more properties here) }; template<> struct my_trait<T&> { typedef T& reference_type; static const bool isReference = true; // ... (possibly more properties here) }; 

上面的特征元函数my_trait<>将引用typesT&和常量布尔值false关联到不是它们自己引用的所有typesT ; 另一方面,它将引用typesT&和常量布尔值true关联到作为引用的所有typesT

举个例子:

 int -> reference_type = int& isReference = false int& -> reference_type = int& isReference = true 

在代码中,我们可以如下所示(下面的所有四行都将被编译,这意味着在static_assert()的第一个参数中expression的条件被满足):

 static_assert(!(my_trait<int>::isReference), "Error!"); static_assert( my_trait<int&>::isReference, "Error!"); static_assert( std::is_same<typename my_trait<int>::reference_type, int&>::value, "Error!" ); static_assert( std::is_same<typename my_trait<int&>::reference_type, int&>::value, "Err!" ); 

在这里你可以看到我使用了标准的std::is_same<>模板,它本身是一个接受两个而不是一个types参数的元函数。 事情可以在这里任意复杂。

尽pipestd::is_same<>type_traits头文件的一部分,但是一些类模板只有在作为元谓语(因此接受一个模板参数)时才被认为是typestraits类。 然而据我所知,术语没有明确的定义。

有关在C ++标准库中使用特征类的示例,请查看input/输出库和string库是如何devise的。


一个政策是略有不同的(实际上,非常不同)。 它通常是一个类,它指定了另外一个通用类的行为应该是关于某些可能以几种不同方式实现的操作(因此,其实现由策略类实现)的行为。

例如,一个通用的智能指针类可以被devise为一个模板类,它接受一个策略作为模板参数来决定如何处理引用计数 – 这只是一个假设的,过于简单化和说明性的例子,所以请尝试抽象从这个具体的代码中着眼于这个机制

这将允许智能指针的devise者不用硬编码承诺参考计数器的修改是否应以线程安全的方式进行:

 template<typename T, typename P> class smart_ptr : protected P { public: // ... smart_ptr(smart_ptr const& sp) : p(sp.p), refcount(sp.refcount) { P::add_ref(refcount); } // ... private: T* p; int* refcount; }; 

在multithreading上下文中,客户端可以使用一个实现智能指针模板的实例化,该策略实现引用计数器的线程安全增量和减量(此处假设为Windows平台):

 class mt_refcount_policy { protected: add_ref(int* refcount) { ::InterlockedIncrement(refcount); } release(int* refcount) { ::InterlockedDecrement(refcount); } }; template<typename T> using my_smart_ptr = smart_ptr<T, mt_refcount_policy>; 

另一方面,在单线程环境中,客户端可以使用策略类来实例化智能指针模板,该策略类只需增加或减less计数器的值即可:

 class st_refcount_policy { protected: add_ref(int* refcount) { (*refcount)++; } release(int* refcount) { (*refcount)--; } }; template<typename T> using my_smart_ptr = smart_ptr<T, st_refcount_policy>; 

这样,图书馆devise人员就提供了一个灵活的解决scheme,能够提供性能和安全性之间的最佳平衡( “不付出不使用的东西” )。

如果您使用ModeT,IsReentrant和IsAsync来控制服务器的行为,那么这是一个策略。

或者,如果你想要一种方式来描述服务器的特性到另一个对象,那么你可以像这样定义一个traits类:

 template <typename ServerType> class ServerTraits; template<> class ServerTraits<Server> { enum { ModeT = SomeNamespace::MODE_NORMAL }; static const bool IsReentrant = true; static const bool IsAsync = true; } 

这里有几个例子来澄清Alex Chamberlain的评论:

特质类的一个常见示例是std :: iterator_traits。 比方说,我们有一些带有成员函数的模板类C,它带有两个迭代器,遍历这些值并以某种方式累积结果。 我们希望积累策略也被定义为模板的一部分,但是会使用策略而不是特质来实现。

 template <typename Iterator, typename AccumulationPolicy> class C{ void foo(Iterator begin, Iterator end){ AccumulationPolicy::Accumulator accumulator; for(Iterator i = begin; i != end; ++i){ std::iterator_traits<Iterator>::value_type value = *i; accumulator.add(value); } } }; 

策略被传递给我们的模板类,而特征是从模板参数派生的。 所以你有什么更类似于一个政策。 在某些情况下,性状更为合适,而政策更为恰当,而且任何一种方法往往都可以达到同样的效果,从而引发关于哪个方面最具performance力的争论。