동적 다형성을 피하기위한 CRTP


답변:


140

두 가지 방법이 있습니다.

첫 번째는 유형의 구조에 대해 인터페이스를 정적으로 지정하는 것입니다.

template <class Derived>
struct base {
  void foo() {
    static_cast<Derived *>(this)->foo();
  };
};

struct my_type : base<my_type> {
  void foo(); // required to compile.
};

struct your_type : base<your_type> {
  void foo(); // required to compile.
};

두 번째는 reference-to-base 또는 pointer-to-base 관용구를 사용하지 않고 컴파일 타임에 연결하는 것입니다. 위의 정의를 사용하면 다음과 같은 템플릿 함수를 가질 수 있습니다.

template <class T> // T is deduced at compile-time
void bar(base<T> & obj) {
  obj.foo(); // will do static dispatch
}

struct not_derived_from_base { }; // notice, not derived from base

// ...
my_type my_instance;
your_type your_instance;
not_derived_from_base invalid_instance;
bar(my_instance); // will call my_instance.foo()
bar(your_instance); // will call your_instance.foo()
bar(invalid_instance); // compile error, cannot deduce correct overload

따라서 함수에서 구조 / 인터페이스 정의와 컴파일 타임 유형 추론을 결합하면 동적 디스패치 대신 정적 디스패치를 ​​수행 할 수 있습니다. 이것이 정적 다형성의 본질입니다.


15
탁월한 답변
Eli Bendersky

5
나는 그것이에서 파생되지 않았고 not_derived_from_base에서 파생되지 않았 음 을 강조하고 싶습니다 ...basebase
leftaroundabout

3
실제로 my_type / your_type 내부의 foo () 선언은 필요하지 않습니다. codepad.org/ylpEm1up (스택 오버플로 발생)-컴파일 타임에 foo 정의를 적용하는 방법이 있습니까? -좋아요, 해결책을 찾았습니다 : ideone.com/C6Oz9-아마도 당신의 대답에서 그것을 고치고 싶을 것입니다.
cooky451

3
이 예에서 CRTP를 사용하는 동기가 무엇인지 설명해 주시겠습니까? bar가 template <class T>로 정의된다면 void bar (T & obj) {obj.foo (); }, 그러면 foo를 제공하는 모든 클래스가 괜찮을 것입니다. 따라서 귀하의 예제에 따르면 CRTP의 유일한 사용은 컴파일 타임에 인터페이스를 지정하는 것입니다. 그게 목적인가요?
Anton Daneyko 2013

1
@Dean Michael 실제로 예제의 코드는 my_type 및 your_type에 foo가 정의되어 있지 않더라도 컴파일됩니다. 이러한 오버라이드가 없으면 base :: foo가 재귀 적으로 호출됩니다 (및 stackoverflows). 그렇다면 cooky451이 보여준대로 답을 정정하고 싶습니까?
Anton Daneyko 2013

18

나는 CRTP에 대한 적절한 토론을 찾고있었습니다. Todd Veldhuizen의 Techniques for Scientific C ++ 는이 (1.3) 및 표현식 템플릿과 같은 다른 많은 고급 기술에 대한 훌륭한 리소스입니다.

또한 Google 도서에서 Coplien의 원본 C ++ Gems 기사 대부분을 읽을 수 있음을 발견했습니다. 아마도 그럴 수도 있습니다.


@fizzer 당신이 제안한 부분을 읽었지만 여전히 template <class T_leaftype> double sum (Matrix <T_leaftype> & A); template <class Whatever> double sum (Whatever & A);
Anton Daneyko 2013

@AntonDaneyko 기본 인스턴스에서 호출 될 때 기본 클래스의 합계가 호출됩니다 (예 : 사각형 인 것처럼 기본 구현으로 "모양의 영역"). 이 경우 CRTP의 목표는 파생 된 동작이 필요할 때까지 사다리꼴을 모양으로 참조 할 수있는 동시에 가장 많이 파생 된 구현, "사다리꼴 영역"등을 해결하는 것입니다. 기본적으로 일반적으로 필요할 때마다 dynamic_cast또는 가상 메서드 가 필요 합니다.
John P

1

나는 CRTP 를 찾아야했다 . 그러나 그렇게 한 후 Static Polymorphism 에 대한 몇 가지 사항을 발견했습니다 . 이것이 귀하의 질문에 대한 대답이라고 생각합니다.

그것은 밝혀 ATL은 매우 광범위하게이 패턴을 사용합니다.


-5

Wikipedia 답변에는 필요한 모든 것이 있습니다. 즉:

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

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

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

이게 실제로 얼마나 당신을 사는지 모르겠지만. 가상 함수 호출의 오버 헤드는 다음과 같습니다 (물론 컴파일러에 따라 다름).

  • 메모리 : 가상 기능 당 하나의 함수 포인터
  • 런타임 : 하나의 함수 포인터 호출

CRTP 정적 다형성의 오버 헤드는 다음과 같습니다.

  • 메모리 : 템플릿 인스턴스화 당 기본 복제
  • 런타임 : 하나의 함수 포인터 호출 + static_cast가 수행하는 모든 작업

4
사실, 템플릿 인스턴스화 당 Base의 복제는 환상입니다. (아직 vtable이없는 경우) 컴파일러는 기본 저장소와 파생 된 저장소를 단일 구조체로 병합하기 때문입니다. 함수 포인터 호출도 컴파일러 (static_cast 부분)에 의해 최적화됩니다.
Dean Michael

19
그건 그렇고, CRTP에 대한 분석이 잘못되었습니다. 딘 마이클이 말했듯이 기억 : 아무것도 아닙니다. 런타임 : 가상이 아닌 하나의 (더 빠른) 정적 함수 호출이 연습의 전체 요점입니다. static_cast는 아무 작업도하지 않고 코드 컴파일 만 허용합니다.
Frederik Slijkerman

2
내 요점은 기본 코드가 모든 템플릿 인스턴스에서 복제된다는 것입니다 (당신이 말하는 바로 병합). 템플릿 매개 변수에 의존하는 하나의 메서드 만있는 템플릿을 갖는 것과 유사합니다. 다른 모든 것은 기본 클래스에서 더 좋습니다. 그렇지 않으면 여러 번 끌어옵니다 ( '병합').
user23167

1
기본의 각 메서드 는 파생 된 각 메서드 에 대해 다시 컴파일됩니다. (예상 된) 각 인스턴스화 된 메서드가 다른 경우 (Derived의 속성이 다르기 때문에), 반드시 오버 헤드로 계산 될 수는 없습니다. 그러나 (일반) 기본 클래스의 복잡한 메서드가 하위 클래스의 가상 메서드를 호출하는 상황에 비해 전체 코드 크기가 더 커질 수 있습니다. 또한 실제로 <Derived>에 전혀 의존하지 않는 Base <Derived>에 유틸리티 메서드를 넣어도 인스턴스화됩니다. 아마도 글로벌 최적화가 그것을 다소 고칠 것입니다.
greggo

여러 계층의 CRTP를 통과하는 호출은 컴파일 중에 메모리에서 확장되지만 TCO 및 인라인을 통해 쉽게 축소 될 수 있습니다. CRTP 자체는 진짜 범인이 아니죠?
John P
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.