Programing

특성과 정책의 차이점은 무엇입니까?

crosscheck 2020. 12. 8. 07:44
반응형

특성과 정책의 차이점은 무엇입니까?


동작을 구성하려는 클래스가 있습니다.

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>의 iterator_traits 및 allocator_traits <memory>입니다. 특성은 클래스 템플릿이므로 전문화 할 수 있습니다. 아래는 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;
};

이 표현식을 std::iterator_traits<T>::value_type사용하면 원시 포인터에도 사용할 수있는 본격적인 반복기 클래스에 대한 일반 코드를 만들 수 있습니다 (원시 포인터에는 멤버가 없기 때문에 value_type).

정책과 특성 간의 상호 작용

고유 한 일반 라이브러리를 작성할 때 사용자가 고유 한 클래스 템플릿을 전문화 할 수있는 방법을 생각하는 것이 중요합니다. 그러나 사용자가 행동을 추출하기보다 주입하기 위해 특성의 전문화를 사용하여 단일 정의 규칙의 희생양이되지 않도록주의해야합니다 . Andrei Alexandrescu 의이 오래된 게시물 을 의역하기 위해

근본적인 문제는 특성의 특수 버전을 보지 못하는 코드가 여전히 컴파일되고 링크 될 가능성이 있으며 때로는 실행될 수도 있다는 것입니다. 이는 명시적인 전문화가 없으면 특수화되지 않은 템플릿이 시작되어 특수한 경우에도 작동하는 일반적인 동작을 구현할 가능성이 있기 때문입니다. 결과적으로 애플리케이션의 모든 코드가 동일한 특성 정의를 보지 못하면 ODR이 위반됩니다.

C ++ 11 std::allocator_traits은 모든 STL 컨테이너가 .NET을 Allocator통해 정책 에서 속성을 추출 할 수 있도록 강제함으로써 이러한 함정을 피합니다 std::allocator_traits<Allocator>. 사용자가 필수 정책 구성원 중 일부를 제공하지 않거나 제공하는 것을 잊은 경우 traits 클래스가 개입하여 누락 된 구성원에 대한 기본값을 제공 할 수 있습니다. allocator_traits자체는 전문화 될 수 없기 때문에 사용자는 컨테이너 메모리 할당을 사용자 정의하기 위해 항상 완전히 정의 된 할당 자 정책을 전달해야하며 자동 ODR 위반이 발생할 수 없습니다.

라이브러리 작성자는 여전히 특성 클래스 템플릿을 전문화 할 수 있지만 (STL이에서하는 것처럼 iterator_traits<T*>) 모든 사용자 정의 전문화를 정책 클래스를 통해 특수 동작을 추출 할 수있는 다중 값 특성으로 전달하는 것이 좋습니다 ( STL이에서하는 것처럼 allocator_traits<A>).

업데이트 : 특성 클래스의 사용자 정의 전문화의 ODR 문제는 주로 특성이 전역 클래스 템플릿 으로 사용될 때 발생하며 향후 모든 사용자가 다른 모든 사용자 정의 전문화를 볼 것이라고 보장 할 수 없습니다. 정책은 로컬 템플릿 매개 변수 이며 모든 관련 정의를 포함하므로 다른 코드에 간섭하지 않고 사용자 정의 할 수 있습니다. 유형 및 상수 만 포함하지만 동작 기능이없는 로컬 템플릿 매개 변수는 여전히 "특성"이라고 할 수 있지만 std::iterator_traits및과 같은 다른 코드에는 표시되지 않습니다 std::allocator_traits.


나는 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)
};

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<>를 받아들이는 메타 함수 인 표준 템플릿을 사용 했음을 알 수 있습니다. 여기서 상황이 임의로 복잡해질 수 있습니다.

Although std::is_same<> is part of the type_traits header, some consider a class template to be a type traits class only if it acts as a meta-predicate (thus, accepting one template parameter). To the best of my knowledge, however, the terminology is not clearly defined.

For an example of usage of a traits class in the C++ Standard Library, have a look at how the Input/Output Library and the String Library are designed.


A policy is something slightly different (actually, pretty different). It is normally meant to be a class that specifies what the behavior of another, generic class should be regarding certain operations that could be potentially realized in several different ways (and whose implementation is, therefore, left up to the policy class).

For instance, a generic smart pointer class could be designed as a template class that accepts a policy as a template parameter for deciding how to handle ref-counting - this is just a hypothetical, overly simplistic, and illustrative example, so please try to abstract from this concrete code and focus on the mechanism.

That would allow the designer of the smart pointer to make no hard-coded commitment as to whether or not modifications of the reference counter shall be done in a thread-safe manner:

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

In a multi-threaded context, a client could use an instantiation of the smart pointer template with a policy that realizes thread-safe increments and decrements of the reference counter (Windows platformed assumed here):

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

In a single-threaded environment, on the other hand, a client could instantiate the smart pointer template with a policy class that simply increases and decreases the counter's value:

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

This way, the library designer has provided a flexible solution that is capable of offering the best compromise between performance and safety ("You don't pay for what you don't use").


If you're using ModeT, IsReentrant, and IsAsync to control the behaviour of the Server, then it's a policy.

Alternatively, if you are want a way to describe the characteristics of the server to another object, then you could define a traits class like so:

template <typename ServerType>
class ServerTraits;

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

Here are a couple of examples to clarify Alex Chamberlain's comment:

A common example of a trait class is std::iterator_traits. Let's say we have some template class C with a member function which takes two iterators, iterates over the values, and accumulates the result in some way. We want the accumulation strategy to be defined as part of the template too, but will use a policy rather than a trait to achieve that.

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

The policy is passed in to our template class, while the trait is derived from the template parameter. So what you have is more akin to a policy. There are situations where traits are more appropriate, and where policies are more appropriate, and often the same effect can be achieved with either method leading to some debate about which is most expressive.

참고URL : https://stackoverflow.com/questions/14718055/what-is-the-difference-between-a-trait-and-a-policy

반응형