特质和政策有什么区别?

Nat*_*mal 59 c++ type-traits policy-based-design template-meta-programming

我有一个类,我试图配置它的行为.

template<int ModeT, bool IsAsync, bool IsReentrant> ServerTraits;
Run Code Online (Sandbox Code Playgroud)

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

template<typename TraitsT>
class Server {...};
Run Code Online (Sandbox Code Playgroud)

我的问题是我上面的用法是我的命名错误吗?我的模板化参数实际上是一个策略而不是特征吗?

什么是模板化论证的特征与政策相比?

Tem*_*Rex 84

政策

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

template<class T, class Allocator = std::allocator<T>> class vector;
Run Code Online (Sandbox Code Playgroud)

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

与基于模板的多态性一样,策略类的接口要求是隐式和语义(基于有效表达式),而不是显式和语法(基于虚拟成员函数的定义).

请注意,较新的无序关联容器具有多个策略.除了通常的Allocator模板参数外,它们还采用Hash默认为std::hash<Key>函数对象的策略.这允许无序容器的用户沿多个正交维度(内存分配和散列)配置它们.

性状

Traits是类模板,用于从泛型类型中提取属性.有两种特征:单值特征和多值特征.单值特征的示例是标题中的特征<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;
};
Run Code Online (Sandbox Code Playgroud)

单值特征通常用于模板元编程和SFINAE技巧,以根据类型条件重载函数模板.

多值特征的实例有从报头中的iterator_traits和allocator_traits <iterator><memory>分别.由于traits是类模板,因此它们可以是专用的.下面是iterator_traitsfor 的专业化示例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;
};
Run Code Online (Sandbox Code Playgroud)

该表达式std::iterator_traits<T>::value_type使得即使对于原始指针也可以使用完整的迭代器类的通用代码成为可能(因为原始指针没有成员value_type).

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

在编写自己的通用库时,重要的是要考虑用户可以专门化自己的类模板的方法.但是,必须要小心,不要让用户通过使用特征的特化来注入而不是提取行为而成为One Definition Rule的受害者.用Andrei Alexandrescu的这句老话来解释

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

C++ 11 std::allocator_traits通过强制执行所有STL容器只能从其Allocator策略中提取属性来避免这些缺陷std::allocator_traits<Allocator>.如果用户选择不提供或忘记提供某些必需的策略成员,则traits类可以介入并为缺少的成员提供默认值.由于allocator_traits本身不能专门化,因此用户必须始终传递完全定义的分配器策略,以便自定义其容器内存分配,并且不会发生静默ODR违规.

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

更新:特征类的用户定义特化的ODR问题主要发生在特征被用作全局类模板时,并且您无法保证所有未来用户都将看到所有其他用户定义的特化.策略是本地模板参数,包含所有相关定义,允许用户定义,而不会干扰其他代码.仅包含类型和常量但不包含行为函数的本地模板参数可能仍被称为"特征",但它们对于其他代码(如std::iterator_traits和)不可见std::allocator_traits.

  • @NateDoromal还要记住,特征和政策的概念是在15年前开发的.从那以后,他们获得了丰富的经验.在最近的STL分配器和迭代器特性中,避免了Alexandrescu发现的ODR问题.标准没有规定命名法,因此请记住语义差异而不是旧式行为与类型/常量区别.政策:注射,本地,可定制; 特征:提取,全局,固定(避免ODR). (4认同)

And*_*owl 24

我想你会在Andrei 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)
};
Run Code Online (Sandbox Code Playgroud)

性状元函数my_trait<>以上相关联的参考类型T&和恒定布尔值false于所有类型的T其是本身的引用; 在另一方面,它关联的引用类型T&和恒定的布尔值true的所有类型T引用.

例如:

int  -> reference_type = int&
        isReference = false

int& -> reference_type = int&
        isReference = true
Run Code Online (Sandbox Code Playgroud)

在代码中,我们可以断言如下(下面的所有四行都将编译,这意味着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!"
    );
Run Code Online (Sandbox Code Playgroud)

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

尽管std::is_same<>type_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;
};
Run Code Online (Sandbox Code Playgroud)

在多线程上下文中,客户端可以使用智能指针模板的实例化,其中的策略实现了引用计数器的线程安全增量和减量(此处假设为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>;
Run Code Online (Sandbox Code Playgroud)

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

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>;
Run Code Online (Sandbox Code Playgroud)

通过这种方式,图书馆设计师提供了一种灵活的解决方案,能够在性能和安全性之间提供最佳折衷("您不需要为不使用的内容付费").

  • 对于`my_traits <T&>`,`reference_type`的typedef是不正确的,它应该是`typedef T&reference_type;`. (2认同)