수색…


소개

클래스가 템플릿 매개 변수 중 하나로서 클래스 템플릿에서 상속하는 패턴입니다. CRTP는 대개 C ++에서 정적 다형성 을 제공하는 데 사용됩니다.

이상하게 반복되는 템플릿 패턴 (CRTP)

CRTP는 컴파일 할 때 유형 특성을 제공하는 데 사용할 수있는 가상 함수와 전통 상속에 대한 강력하고 정적 인 대안입니다. 템플릿 매개 변수 중 하나 인 파생 클래스를 취하는 기본 클래스 템플릿을 사용하여 작동합니다. 이것에 의해, 파생 클래스에 대한 this 포인터의 static_cast 를 합법적으로 실행할 수 있습니다.

물론 이것은 CRTP 클래스가 항상 다른 클래스의 기본 클래스로 사용되어야한다는 것을 의미합니다. 파생 클래스는 자신을 기본 클래스로 전달해야합니다.

C ++ 14

begin()end() 함수를 모두 지원하는 컨테이너 세트가 있다고 가정 해 보겠습니다. 컨테이너에 대한 표준 라이브러리의 요구 사항에는 더 많은 기능이 필요합니다. begin()end() 에만 기반하여 해당 기능을 제공하는 CRTP 기본 클래스를 디자인 할 수 있습니다.

#include <iterator>
template <typename Sub>
class Container {
  private:
    // self() yields a reference to the derived type
    Sub& self() { return *static_cast<Sub*>(this); }
    Sub const& self() const { return *static_cast<Sub const*>(this); }

  public:
    decltype(auto) front() {
      return *self().begin();
    }

    decltype(auto) back() {
      return *std::prev(self().end());
    }

    decltype(auto) size() const {
      return std::distance(self().begin(), self().end());
    }

    decltype(auto) operator[](std::size_t i) {
      return *std::next(self().begin(), i);
    }
};

위의 클래스는 begin()end() 를 제공하는 모든 하위 클래스에 대해 front() , back() , size()operator[] 함수를 제공 begin() . 예제 서브 클래스는 간단한 동적으로 할당 된 배열입니다.

#include <memory>
// A dynamically allocated array
template <typename T>
class DynArray : public Container<DynArray<T>> {
  public:
    using Base = Container<DynArray<T>>;

    DynArray(std::size_t size)
      : size_{size},
      data_{std::make_unique<T[]>(size_)}
    { }

    T* begin() { return data_.get(); }
    const T* begin() const { return data_.get(); }
    T* end() { return data_.get() + size_; }
    const T* end() const { return data_.get() + size_; }

  private:
    std::size_t size_;
    std::unique_ptr<T[]> data_;
};

DynArray 클래스 사용자는 다음과 같이 CRTP 기본 클래스에서 제공하는 인터페이스를 쉽게 사용할 수 있습니다.

DynArray<int> arr(10);
arr.front() = 2;
arr[2] = 5;
assert(arr.size() == 10);

유용성 : 이 패턴은 특히 상속 계층 구조를 따라가는 정적 함수에 의존하는 런타임에 가상 함수 호출을 피합니다.

DynArray<int> arr(10);
DynArray<int>::Base & base = arr;
base.begin(); // no virtual calls

Container<DynArray<int>> 기본 클래스의 begin() 함수 내에서 유일한 정적 캐스트는 컴파일러가 코드를 크게 최적화 할 수있게하며 런타임에 가상 테이블 조회를하지 않습니다.

제한 사항 : 기본 클래스는 두 개의 다른 DynArray 대해 템플리트되고 서로 다르기 때문에 기본 클래스가 파생 클래스에 종속되지 않는 정상 상속으로 일반적으로 수행 할 수있는 것처럼 유형 동질 배열에있는 기본 클래스에 대한 포인터를 저장할 수 없습니다 유형:

class A {};
class B: public A{};

A* a = new B;

코드 복제를 피하기위한 CRTP

방문자 패턴 의 예는 CRTP에 대한 매력적인 유스 케이스를 제공합니다.

struct IShape
{
    virtual ~IShape() = default;

    virtual void accept(IShapeVisitor&) const = 0;
};

struct Circle : IShape
{
    // ...        
    // Each shape has to implement this method the same way
    void accept(IShapeVisitor& visitor) const override { visitor.visit(*this); }
    // ...
};

struct Square : IShape
{
    // ...    
    // Each shape has to implement this method the same way
    void accept(IShapeVisitor& visitor) const override { visitor.visit(*this); }
    // ...
};

각 자식 형식의 IShape 는 같은 방식으로 동일한 함수를 구현해야합니다. 그건 여분의 타이핑입니다. 대신 우리는 계층 구조에 새로운 유형을 도입하여이를 수행합니다.

template <class Derived>
struct IShapeAcceptor : IShape {
    void accept(IShapeVisitor& visitor) const override {
        // visit with our exact type
        visitor.visit(*static_cast<Derived const*>(this));
    }
};

그리고 이제 각 셰이프는 단순히 수락 자로부터 상속해야합니다.

struct Circle : IShapeAcceptor<Circle>
{
    Circle(const Point& center, double radius) : center(center), radius(radius) {}
    Point center;
    double radius;
};

struct Square : IShapeAcceptor<Square>
{
    Square(const Point& topLeft, double sideLength) : topLeft(topLeft), sideLength(sideLength) {}    
    Point topLeft;
    double sideLength;
};

중복 코드는 필요하지 않습니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow