C++
이상하게 반복되는 템플릿 패턴 (CRTP)
수색…
소개
클래스가 템플릿 매개 변수 중 하나로서 클래스 템플릿에서 상속하는 패턴입니다. CRTP는 대개 C ++에서 정적 다형성 을 제공하는 데 사용됩니다.
이상하게 반복되는 템플릿 패턴 (CRTP)
CRTP는 컴파일 할 때 유형 특성을 제공하는 데 사용할 수있는 가상 함수와 전통 상속에 대한 강력하고 정적 인 대안입니다. 템플릿 매개 변수 중 하나 인 파생 클래스를 취하는 기본 클래스 템플릿을 사용하여 작동합니다. 이것에 의해, 파생 클래스에 대한 this
포인터의 static_cast
를 합법적으로 실행할 수 있습니다.
물론 이것은 CRTP 클래스가 항상 다른 클래스의 기본 클래스로 사용되어야한다는 것을 의미합니다. 파생 클래스는 자신을 기본 클래스로 전달해야합니다.
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;
};
중복 코드는 필요하지 않습니다.