2010-03-12 6 views
5

질문을 위해 크게 단순화되었습니다. 템플릿 매개 변수를 기반으로 C++에서 동적 디스패치를 ​​에뮬레이트합니다.

struct Base { 
    virtual int precision() const = 0; 
}; 

template<int Precision> 
struct Derived : public Base { 

    typedef Traits<Precision>::Type Type; 

    Derived(Type data) : value(data) {} 
    virtual int precision() const { return Precision; } 

    Type value; 

}; 

내가 서명과 템플릿이 아닌 기능을 원하는 : 내가 계층 구조가 말해 함수의 결과의 특정 유형이 동일한 유형 인

Base* function(const Base& a, const Base& b); 

을의 중 등 ab은 더 큰 것 Precision; 다음 의사 코드 같은 : AB 각각 ab의 특정 종류

Base* function(const Base& a, const Base& b) { 

    if (a.precision() > b.precision()) 

     return new A(((A&)a).value + A(b.value).value); 

    else if (a.precision() < b.precision()) 

     return new B(B(((A&)a).value).value + ((B&)b).value); 

    else 

     return new A(((A&)a).value + ((A&)b).value); 

} 

. functionDerived의 인스턴스가 얼마나 많은 지 독립적으로 작동하고 싶습니다. typeid() 비교의 방대한 테이블을 피하고 싶습니다. RTTI는 답이 좋습니다. 어떤 아이디어?

+2

당신은 완전한 클래스 유형을 모른다는 것을 언급해야한다고 생각합니다. 여러분은 단지'Base &'를 알고 있습니다. 내 자신을 포함 해 여러 답변이 'Derived '이라는 정확한 유형을 알고 있다고 가정했습니다. –

+0

답변에 대한 의견에서 주목하기 : 추가 요구 사항은 함수가 템플릿이 될 수 없다는 것입니다. 해당 Base * (Base &, Base &) 서명이 있어야합니다. –

+0

질문에 더 명확하게 제한 사항을 포함 시켰습니다. –

답변

3

템플릿이 컴파일 타임에 확장되고 컴파일 타임에 function()이 파생 된 것을 알지 못하기 때문에 가능한 모든 유형의 대규모 목록 중에서 선택하지 않고 templateed 코드에 function() 위임을 직접 수행 할 수 없습니다. 유형은 실제로 호출됩니다. 템플리트 화 된 operation 함수의 모든 버전에 대해 템플리트 된 코드의 인스턴스화를 컴파일 할 필요가 있습니다. 이는 잠재적으로 무한한 세트입니다.

논리에 따르면 필요한 모든 템플릿을 알고있는 유일한 곳은 Derived 클래스입니다.당신과 같이 function을 정의하고, 간접적으로 발송을 할 수있는, 그런

Derived<Precision> *operation(Base& arg2) { 
    Derived<Precision> *ptr = new Derived<Precision>; 
    // ... 
    return ptr; 
} 

: 따라서, 귀하의 Derived 클래스는 멤버를 포함해야한다이 단순화 된 버전인지

Base* function(const Base& a, const Base& b) { 
    if (a.precision() > b.precision()) 
    return a.operation(b); 
    else 
    return b.operation(a); 
} 

참고; 귀하의 작업이 인수에서 대칭이 아닌 경우 두 번째 버전의 멤버 함수를 정의해야합니다. 첫 번째 인수 대신 this을 사용하고 두 번째 버전 대신 두 번째 버전을 사용합니다.

a.operationb의 파생 형식을 모르면 b.value의 적절한 양식을 얻는 방법이 필요하다는 사실을 무시했습니다. 직접 해결해야 할 것입니다. 런타임시 디스패치하기 때문에 형식을 b으로 템플릿 화하여이 문제를 해결하는 것은 불가능합니다 (이전과 동일한 논리로). 이 솔루션은 정확히 어떤 유형인지에 따라 달라지며 더 높은 정밀도 유형이 해당 객체의 정확한 유형을 알지 못해도 동일하거나 낮은 정밀도 인 Derived 객체에서 값을 가져올 수있는 방법이 있는지 여부에 따라 달라집니다. 이것은 가능하지 않을 수도 있습니다.이 경우 유형 ID에 일치하는 긴 목록이 있습니다.

당신은하지만, switch 문에 그렇게 할 필요가 없습니다. 각각의 Derived 유형에 업 스케일링을위한 멤버 함수 세트를 더 정확하게 제공 할 수 있습니다. 예를 들어 :

template<int i> 
upCast<Derived<i> >() { 
    return /* upcasted value of this */ 
} 

는 그런 다음 operator 멤버 함수는 b.upcast<typeof(this)>에서 동작 할 수 있으며, 명시 적으로 필요로하는 유형의 값을 얻기 위해 캐스팅을 할 필요가 없습니다. 이러한 함수 중 일부를 명시 적으로 인스턴스화해야 컴파일 할 수 있습니다. 나는 확실히 말할 RTTI와 충분한 작업을하지 않았습니다.

그러나 근본적으로 N 가능한 정밀도가 있다면 N 개의 가능한 조합이 있으며 실제로 각각 별도로 컴파일 된 코드가 있어야합니다. 당신이 function의 사용자 정의 템플릿을 사용할 수없는 경우, 당신은이 모든 가능성 N N의 컴파일 된 버전을 가지고 있고, 어떻게 든 당신은 그들 모두를 생성하는 컴파일러에게해야하고, 어떻게 든 당신은 바로를 선택해야 하나는 런타임에 디스패치합니다. 멤버 함수를 사용하는 트릭은 N의 요소 중 하나를 취하지 만, 나머지 요소는 그대로 남아 있으며 전체적으로 그것을 만들 수있는 방법은 없습니다.

+0

이것은 나를 위해 충분하다.두 번째 요소는 'Base' 임의 유형의 런타임에 변환하는 기능을 정의한다는 사실에 의해 제거된다. –

+0

오, 좋다! 그런 시설이 두 번째 요소를 제거하는 데 유용 할 것이라고 제안하는 편집이었습니다. –

1

먼저, precision 구성원을 함수가 아닌 static const int 값으로 만들고 싶다면 컴파일 타임에이 값을 조작 할 수 있습니다. Derived, 그것은 다음과 같습니다

static const int precision = Precision; 

그런 다음, 당신은 가장 정확한 자료/파생 클래스를 결정하기 위해 헬퍼 구조체가 필요합니다. 첫째, 당신은 부울에 따라 두 가지 유형 중 하나를 선택하는 일반적인 헬퍼 헬퍼 구조체가 필요합니다 use_firstT2에, 그렇지 않으면 true이며, 경우

template<typename T1, typename T2, bool use_first> 
struct pickType { 
    typedef T2 type; 
}; 

template<typename T1, typename T2> 
struct pickType<T1, T2, true> { 
    typedef T1 type; 
}; 

그런 pickType<T1, T2, use_first>::typeT1에 해결됩니다.

template<typename T1, typename T2> 
struct mostPrecise{ 
    typedef pickType<T1, T2, (T1::precision > T2::precision)>::type type; 
}; 

지금, mostPrecise<T1, T2>::type는 두 종류의 큰 precision 값을 갖는 중 당신을 줄 것이다 : 그래서, 우리는 가장 정확한 유형을 선택하려면이 옵션을 사용합니다. 당신이 그것을 가지고

template<typename T1, typename T2> 
mostPrecise<T1, T2>::type function(const T1& a, const T2& b) { 
    // ... 
} 

그리고 거기 : 그래서, 당신은 당신의 기능을 정의 할 수 있습니다.

+0

이것은 다른 프로젝트에서 매우 흥미롭고 유용하지만 런타임에 결정해야 할 부분이 필요합니다. –

+0

감사합니다. 이 값을 삭제하는 것이 아니라 역사적인 가치로 남겨 두겠다.하지만 런타임에 이것을 수행하는 것에 대한 다른 대답을 참조하라. –

4

당신이 multimethods 일명, multiple dispatch라고 요구하고있다. C++ 언어의 기능은 아닙니다.

특별한 경우에 대한 해결 방법이 있습니다,하지만 당신은 몇 가지 구현 자신을 일을 피할 수 없다. 여러 파견

하나 개의 일반적인 패턴은 "재발 dispatch", 일명 "재귀 이연 파견"라고합니다. 기본적으로 하나의 가상 메소드는 하나의 매개 변수 유형을 해결 한 다음 모든 매개 변수가 해결 될 때까지 다른 가상 메소드를 호출합니다. 외부의 기능 (있는 경우)은 이러한 가상 메소드 중 첫 번째를 호출합니다. N이 유도 된 클래스를 가정

,이 최초의 파라미터를 해결하는 하나 개의 방법이 될 것이다 N 등등 제 등을 해결하기 위해 상기 제 N 개의 * n을 해결하기 위해 - 최악 어쨌든. 이는 상당한 수작업으로, typeid를 기반으로하는 조건부 블록을 사용하는 것이 처음 개발하는 경우 더 쉬울 수도 있지만 재 구현을 사용하는 유지 관리가 더 강력합니다.

class Base; 
class Derived1; 
class Derived2; 

class Base 
{ 
    public: 
    virtual void Handle (Base* p2); 

    virtual void Handle (Derived1* p1); 
    virtual void Handle (Derived2* p1); 
}; 

class Derived1 : public Base 
{ 
    public: 
    void Handle (Base* p2); 

    void Handle (Derived1* p1); 
    void Handle (Derived2* p1); 
}; 

void Derived1::Handle (Base* p2) 
{ 
    p2->Handle (this); 
} 

void Derived1::Handle (Derived1* p1) 
{ 
    // p1 is Derived1*, this (p2) is Derived1* 
} 

void Derived1::Handle (Derived2* p1) 
{ 
    // p1 is Derived2*, this (p2) is Derived1* 
} 

// etc 

어려울 것 파생 클래스에 대한 템플릿을 사용하여이 구현하고, 아마, 읽을 이상 유지할 매우 깨지기 쉬운 것 처리 할 수있는 템플릿 메타 프로그래밍. 비 템플릿 메소드를 사용하여 디스패치를 ​​구현 한 다음 추가 기능으로 확장하는 mixin 템플릿 (기본 클래스를 템플릿 매개 변수로 사용하는 템플릿 클래스)을 사용하면 그리 나쁘지는 않을 수 있습니다.

visitor design pattern

밀접 (기본적하여 구현)를 재 전달할 IIRC에 관한 것이다. ++

다른 방법은 문제를 처리 할 수 ​​있도록 설계 언어를 사용하는 것입니다, 그리고 C와 함께 잘 작동 몇 가지 옵션이 있습니다. 하나는 treecc - AST 노드 및 lex 및 yacc와 마찬가지로 출력으로 "소스 코드"를 생성하는 다중 발송 작업을 처리하기위한 도메인 별 언어를 사용하는 것입니다. 그냥 간단하게 동적으로 입력 된 값 클래스 ID, IYSWIM을 할 수 있습니다 -

는 파견 결정을 처리하기 위해 수행 모든

는 AST 노드 ID를 기반으로 스위치 문을 생성합니다. 그러나 이것들은 당신이 쓰거나 유지할 필요가없는 switch 문이다. 이것은 중요한 차이점이다. 내가 가지고있는 가장 큰 문제는 AST 노드가 자신의 소멸자 처리를 조작했다는 것입니다. 즉, 특별한 노력을하지 않으면 멤버 데이터에 대한 소멸자가 호출되지 않습니다. 즉, 필드의 POD 유형에 가장 적합합니다.

다른 옵션은 다중 방법을 지원하는 언어 전 처리기를 사용하는 것입니다. Stroustrup은 한 곳에서 여러 방법을 지원하기위한 아이디어가 상당히 발달했기 때문에 부분적으로 이러한 것들이 몇 가지 있습니다. CMM은 하나입니다. Doublecpp이 또 하나입니다. 또 다른 하나는 Frost Project입니다. 나는 CMM이 Stroustrup이 설명한 것에 가장 가깝다고 생각하지만, 나는 체크하지 않았다.

는 궁극적으로하지만, 여러 파견은 런타임 결정을 내릴 수있는 방법이며, 같은 결정을 처리하는 방법에는 여러 가지가 있습니다. 전문가 DSL은 상당한 양의 번거 로움을 가져 오므로 일반적으로 여러 번 발송해야하는 경우에만 수행합니다. Redispatch와 방문자 패턴은 강력한 WRT 유지 관리이지만 복잡성과 복잡함을 희생합니다. 컴파일 타임에 처리되지 않은 사건의 가능성을 탐지하는 것이 불가능하지는 않더라도 어렵다는 것을 명심하면서 간단한 조건문이 간단한 경우에 더 나은 선택 일 수 있습니다.

자주의 경우와 마찬가지로

는 ++ 적어도 C에, 그것을 아무도 올바른 방법이 없습니다.

관련 문제