2017-05-06 19 views
8

CRTP에서 머리를 감싸려고합니다. 이 포럼을 포함하여 좋은 소식이 몇 군데 있지만 정적 다형성의 기본 사항에 대해 혼란 스럽습니다. 다음 위키 백과 항목을 보면 :CRTP 정적 다형성에 대한 혼동

template <class T> 
struct Base 
{ 
    void implementation() 
    { 
     // ... 
     static_cast<T*>(this)->implementation(); 
     // ... 
    } 

static void static_func() 
{ 
    // ... 
    T::static_sub_func(); 
     // ... 
    } 
}; 

struct Derived : public Base<Derived> 
{ 
    void implementation(); 
    static void static_sub_func(); 
}; 

내가이 좀 컴파일 타임 가상 함수처럼() 파생 클래스에서 변형 내가 다른 구현을 가지고하는 데 도움이 이해합니다. 하지만, 내 혼란 내가 정상적인 상속으로 인한 자료에 가상 함수, 때와 템플릿 것으로 나는

void func(Base x){ 
    x.implementation(); 
} 

같은 기능을 가질 수 없습니다 생각,하지만 난에이 중 하나를 지정합니다

func(Derived x) 

또는 단순히 파생 :: 자료의 간단한 방법을 구현/그림자 반대로

template<class T> 
func(T x) 

은 그래서 CRTP 실제로, 이러한 맥락에서 저를 구입 않습니다 사용할 수 있습니까? 하나 개 이상의 기능이 포함될 때 CRTP의

struct Base 
{ 
    void implementation(); 

struct Derived : public Base 
{ 
    void implementation(); 
    static void static_sub_func(); 
}; 
+0

논의, 당신은 [객체 슬라이스] 고통 (https://en.wikipedia.org/wiki/Object_slicing). 다형성 (polymorphic) 동작 (정적 또는 동적 다형성 사용 여부)을 원할 경우 참조 또는 포인터로 전달하십시오. –

+0

나를 위해, 작동하지 않는다 변수 또는 필드 'func'void가 선언 된 void func (Base & x) ... – user32849

+0

@ user32849'Base'는 템플릿입니다. 'Base &'만 사용할 수는 없으므로 템플릿 인자'Base &'를 입력해야합니다. 이는'func'도 템플릿이어야한다는 것을 의미합니다. – Angew

답변

5

장점은 분명이됩니다. 인해 C++의 타입 시스템의 정적 특성은 호출에

0

[Live example]

:

struct Base 
{ 
    int algorithm(int x) 
    { 
    prologue(); 
    if (x > 42) 
     x = downsize(x); 
    x = crunch(x); 
    epilogue(); 
    return x; 
    } 

    void prologue() 
    {} 

    int downsize(int x) 
    { return x % 42; } 

    int crunch(int x) 
    { return -x; } 

    void epilogue() 
    {} 
}; 

struct Derived : Base 
{ 
    int downsize(int x) 
    { 
    while (x > 42) x /= 2; 
    return x; 
    } 

    void epilogue() 
    { std::cout << "We're done!\n"; } 
}; 

int main() 
{ 
    Derived d; 
    std::cout << d.algorithm(420); 
} 

이 출력 (아니오 CRTP으로)이 코드를 고려 d.algorithmBase에서 모든 기능을 호출합니다. Derived에 시도 된 재정의는 호출되지 않습니다.

이 CRTP 사용하는 경우 변경 :

template <class Self> 
struct Base 
{ 
    Self& self() { return static_cast<Self&>(*this); } 

    int algorithm(int x) 
    { 
    self().prologue(); 
    if (x > 42) 
     x = self().downsize(x); 
    x = self().crunch(x); 
    self().epilogue(); 
    return x; 
    } 

    void prologue() 
    {} 

    int downsize(int x) 
    { return x % 42; } 

    int crunch(int x) 
    { return -x; } 

    void epilogue() 
    {} 
}; 

struct Derived : Base<Derived> 
{ 
    int downsize(int x) 
    { 
    while (x > 42) x /= 2; 
    return x; 
    } 

    void epilogue() 
    { std::cout << "We're done!\n"; } 
}; 

int main() 
{ 
    Derived d; 
    std::cout << d.algorithm(420); 
} 

출력 :

는 우리가 완료!
-26

[Live example]

이 방법 Base의 구현은 실제로 Derived가 제공 될 때마다 Derived으로 호출 "재정의를."

원래 코드에서도 볼 수 있습니다. Base이 CRTP 클래스가 아닌 경우 static_sub_func으로 전화하면 Derived::static_sub_func으로 변환되지 않습니다.다른 방법을 통해 CRTP의 장점이 무엇인지에 관해서는


:

  • CRTP virtual 대 기능 :

    CRTP가 관련된 런타임 오버 헤드가 없습니다 의미, 컴파일시 구조입니다. 기본 클래스 참조 (일반적으로)를 통해 가상 함수를 호출하면 함수를 호출하는 포인터가 필요하므로 간접 비용이 발생하고 인라인이 수행되지 않습니다.

  • CRTP 단순히 Derived 모든 것을 구현 대 :

    자료 클래스 코드 재사용을.

물론 CRTP는 순전히 컴파일 타임 구조입니다. 컴파일 타임 다형성을 실현하려면 컴파일시 다형성 구문 인 템플릿을 사용해야합니다. 이 당신이 할 수있는 두 가지 방법은 다음과 같습니다

template <class T> 
int foo(Base<T> &actor) 
{ 
    return actor.algorithm(314); 
} 

template <class T> 
int bar(T &actor) 
{ 
    return actor.algorithm(314); 
} 

이전에 대응보다 밀접하게 다형성을 런타임하기가 더 나은 형태의 안전성을 제공하며, 후자를 기반으로 더 오리 타이핑이다.

+0

이것은 분명히 CRTP의 큰 사용이지만 CRTP의 문제는 일반적으로 CRTP의 "정적 다형성"에 대한 설명은 CRTP가 다형성과 직접 관련이 없다는 것입니다. 구현을 돕는 도구입니다. –

+0

@NirFriedman 다형성과 관련이 있지만 * 컴파일 타임 * 다형성이 있습니다. 템플릿을 통해서만 실현 될 수 있습니다. 나는이 점에 대해 답을 추가 할 것이다. – Angew

+0

다형성 컴파일 시간을 이해합니다. 네, 템플릿에 클래스가 있습니다. 그러나 CRTP는 인터페이스에 따라 유형을 사용하는 함수에 관한 것이 아니라 다형성입니다. Base는 Derived 또는 유사한 클래스를 구현하는 데 도움이되는 도구 일뿐입니다. 구현에 도움이된다면 실제로 다형성에 관한 것이 아니라 인터페이스에 관한 것입니다. –

6

"정적 다형성"과 같은 CRTP의 설명은 CRPT가 실제로 사용되는 것과 관련하여 실제로 도움이되지 않거나 정확하지 않습니다. 다형성은 실제로 동일한 인터페이스 또는 계약을 수행하는 다른 유형을 갖는 것입니다. 이러한 다른 유형이 해당 인터페이스를 구현하는 방법은 다형성과 직각을 이룹니다. 동적 다형성은 다음과 같습니다

Animal 가상 make_sound 방법을 제공하는 기본 클래스입니다
void foo(Animal& a) { a.make_sound(); } // could bark, meow, etc 

, 그 Dog, Cat, 등, 재정. 다음은 정적 다형성입니다.

template <class T> 
void foo(T& a) { a.make_sound(); } 

그게 전부입니다. foo의 정적 버전을 기본 클래스에서 상속하지 않고 make_sound 메서드를 정의하는 모든 유형에서 호출 할 수 있습니다. 그러면 컴파일 타임에 통화가 해결됩니다 (즉 vtable 통화 요금을 지불하지 않음).

그럼 CRTP는 어디에 적합합니까? CRTP는 실제로 인터페이스에 관한 것이 아니기 때문에 다형성에 관한 것이 아닙니다. CRTP는 일을보다 쉽게 ​​구현할 수있게 해줍니다. CRTP가 마술을 일으키는 이유는 파생 된 유형이 제공하는 모든 것에 대한 완전한 지식과 함께 유형의 인터페이스에 직접 사물을 주입 할 수 있다는 것입니다. 간단한 예는 다음과 같을 수 있습니다

template <class T> 
struct MakeDouble { 
    T double() { 
     auto& me = static_cast<T&>(*this); 
     return me + me; 
}; 

지금 가산 연산자를 정의하는 모든 클래스가, 또한 double 방법을 제공 할 수 있습니다

class Matrix : MakeDouble<Matrix> ... 

Matrix m; 
auto m2 = m.double(); 

CRTP는 인터페이스하지, 모든 구현을 돕는에 관한 것입니다. 그래서 종종 "정적 다형성 (static polymorphism)"이라고 불리는 사실에 너무 매달 리지 마십시오. CRTP가 사용될 수있는 실제 표준 예제를 원한다면 Andrei Alexandrescu의 Modern C++ 디자인 1 장을 고려하십시오. 그래도 천천히 :-).

+0

CRTP는 여러 용도로 사용됩니다. 이것도 그 중 하나지만, asker가 말하는 것은 유효합니다. –

0

당신은 어느 쪽도

void func(Base x); 

또는

void func(Derived x); 

당신에게 정적 다형성을 제공 없다고 정확합니다. Base은 유형이 아니며 두 번째 유형은 다형성이 아니기 때문에 첫 번째 컴파일은 컴파일되지 않습니다.

그러나 두 개의 파생 클래스 인 Derived1Derived2이 있다고 가정합니다. 그런 다음 func 그 자체를 템플릿으로 만들어보세요.

template <typename T> 
void func(Base<T>& x); 

Base에서 파생 된 모든 유형의 호출 할 수 있으며, 매개 변수를 호출하는 기능을 결정하기 위해 전달되는 어떤의 정적 형식을 사용합니다.


이것은 CRTP의 용도 중 하나 일 뿐이므로 나는 추측하기에 덜 일반적인 것을 말합니다. Nir Friedman은 정적 다형성과 관련이없는 또 다른 대답에서 제안하는대로 사용할 수도 있습니다. 당신이 값에 의해`Base` 인스턴스를 촬영하면

모두 사용이 아주 잘 here