我有一个类,它的行为我想配置。
template<int ModeT, bool IsAsync, bool IsReentrant> ServerTraits;
再后来,我有我的服务器对象本身:
template<typename TraitsT>
class Server {...};
我的问题是我使用以上是我命名名不副实? 是我的模板参数实际上是政策,而不是一个特质?
当一个模板参数的性状与政策?
我有一个类,它的行为我想配置。
template<int ModeT, bool IsAsync, bool IsReentrant> ServerTraits;
再后来,我有我的服务器对象本身:
template<typename TraitsT>
class Server {...};
我的问题是我使用以上是我命名名不副实? 是我的模板参数实际上是政策,而不是一个特质?
当一个模板参数的性状与政策?
政策的类(或类模板)通常通过继承行为注入到一个父类,。 通过分解一个父接口成正交(独立的)尺寸,政策类构成的更复杂的界面的构建块。 一个经常看到图案是与库提供的默认提供政策用户可定义的模板(或模板的模板)的参数。 标准库的一个例子是分配器,这是所有STL容器的策略模板参数
template<class T, class Allocator = std::allocator<T>> class vector;
在这里, Allocator
模板参数(这本身也是一个类模板!)注入的内存分配和释放政策进入父类std::vector
。 如果用户不提供一个分配器,默认std::allocator<T>
被使用。
由于是基于模板的polymporphism典型,对政策类的接口要求是隐式和语义 (基于有效的表达式),而不是明确的和句法(基于虚成员函数的定义)。
需要注意的是更近的无序关联式容器,有一个以上的政策。 除了通常的Allocator
模板参数,他们也采取了Hash
政策默认std::hash<Key>
函数对象。 这允许无序容器的用户沿着多个正交维度(存储器分配和散列)配置它们。
特质类模板来提取一个泛型类型的属性 。 有两种特质:单值特性和多值特征。 单值性状的实例是那些从报头<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技巧用于重载基于一种状态函数模板。
多值特征的实例有从报头中的iterator_traits和allocator_traits <iterator>
和<memory>
分别。 由于性状类模板,他们可以专门。 下面的专业化的一个例子iterator_traits
为T*
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;
};
表达std::iterator_traits<T>::value_type
使得有可能使对于甚至对于原始指针可用全面迭代类属代码(因为原始指针没有构件value_type
)。
当编写自己的通用库,它想办法用户可以专注自己的类模板是很重要的。 一个人要小心,但是,不要让用户使用特点的专业化注入,而不是提取行为牺牲品的一个定义规则 。 对此解释为老后由安德烈Alexandrescu的
最根本的问题是,代码无法看到性状的特殊版本仍将编译,很可能链接,有时甚至可能运行。 这是因为在没有明确的专业化,非专业化的模板踢,容易实现,对于你的特殊情况,以及工作的一般行为。 因此,如果不是所有的应用程序中的代码看到一个特点相同的定义,在ODR违反。
在C ++ 11 std::allocator_traits
通过强制所有STL容器可以从他们只能提取属性避免了这些缺陷Allocator
政策通过std::allocator_traits<Allocator>
。 如果用户不选择或忘记提供一些必要的政策成员,性状类可以介入并提供缺省值对于那些缺少的成员。 由于allocator_traits
本身不能专业化,用户不得不经常通过完全定义的分配政策,以定制自己的容器内存分配,并且不会发生沉默的ODR违反。
需要注意的是作为一个库作家,一个仍然可以专注traits类模板(如STL中确实iterator_traits<T*>
但它是通过策略类,所有用户定义的专业化成多值特性,可以很好的做法提取专门的行为(如STL中确实allocator_traits<A>
)。
UPDATE:性状时的性状作为全球一流的模板 ,你不能保证所有未来的用户将看到所有其他用户定义的专门化课程主要是发生的用户自定义专业化的ODR问题。 策略是本地模板参数 ,并包含所有相关的定义,允许它们被用户定义的,而不在其他的码间干扰。 仅包含类型和常量-但本地模板参数没有行为上职能-可能仍然被称为“特质”,但他们不会是像其他代码可见std::iterator_traits
和std::allocator_traits
。
我想你会发现最好的回答你的问题这本书由安德烈Alexandrescu的 。 在这里,我会尽量给只是一个简短的概述。 希望这会有所帮助。
甲性状类是通常旨在是一个元函数关联类型的其它类型或为恒定值,以提供这些类型的表征类。 换句话说,它是类型属性模特的一种方式。 该机构通常利用模板和模板专业化来定义的关联:
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<>
以上联营引用类型T&
与恒定布尔值false
所有类型T
其不是本身的引用; 在另一方面,它关联的引用类型T&
与恒定布尔值true
所有类型T
是引用。
因此,举例来说:
int -> reference_type = int&
isReference = false
int& -> reference_type = int&
isReference = true
在代码中,我们可以断言上面如下(所有下面的四个行会编译,这意味着在第一个参数表示的条件,以static_assert()
满足):
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<>
模板,它本身就是一个元函数,接受两个 ,而不是一个类型参数。 事情可以在这里任意复杂。
虽然std::is_same<>
是的一部分type_traits
头,一些考虑类模板是仅当它作为一个元谓词类型traits类(因此,接受一个模板参数)。 据我所知,然而,术语没有明确规定。
对于C ++标准库中的traits类的使用的一个例子,看看如何输入/输出库和字符串库的设计。
策略是什么略有不同(实际上是相当不同的)。 它通常意味着是一个类,它指定的另一行为,泛型类应该是有关可能在几种不同的方式(和它的实现,因此,留给了策略类)来实现潜在的某些操作的东西。
举例来说,通用的智能指针类可以被设计成接受一个政策来决定如何处理裁判计数模板参数模板类 - 这只是一个假设,过于简单,和说明性的例子,所以请尽量抽象从机制这个具体的代码和重点。
这将使智能指针的设计,使没有硬编码的承诺是否或不引用计数器的修改都应以线程安全的方式来完成:
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;
};
在多线程环境中,客户端可以使用智能指针模板的实例化与实现线程安全的增量和参考计数器的递减政策(视窗platformed这里假设):
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>;
在单线程环境中,另一方面,客户端可以发起与策略类,简单地增加和减少计数器的值的智能指针模板:
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>;
这样一来,图书馆的设计师提供了一个灵活的解决方案,能够提供性能和安全(“你不付你不要用什么”)之间的最佳妥协。
如果您使用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;
}
这里有几个例子来阐明亚历克斯·张伯伦的评论:
性状类的一个常见的例子是标准:: 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);
}
}
};
该政策在我们的模板类过去了,而该性状是由模板参数的。 所以,你有什么是更类似于政策。 在有些情况下的特征是比较合适的情况下,并在政策,都比较合适,而且往往同样的效果可以用两种方法导致一些争论哪些是最具表现力来实现。