클래스 멤버 함수 템플릿이 가상 일 수 있습니까?


304

C ++ 클래스 멤버 함수 템플릿은 가상이 될 수 없다고 들었습니다. 이것이 사실입니까?

이들이 가상 일 수 있다면, 그러한 기능을 사용하는 시나리오의 예는 무엇입니까?


12
나는 비슷한 문제에 직면했으며 동시에 가상과 템플릿이되는 것이 논쟁의 여지가 있음을 알게되었습니다. 내 솔루션은 파생 클래스에서 공통적 인 템플릿 마법을 작성하고 특수한 부분을 수행하는 순수한 가상 함수를 호출하는 것이 었습니다. 이것은 물론 내 문제의 성격과 관련이 있으므로 모든 경우에 작동하지 않을 수 있습니다.
Tamás Szelei

답변:


329

템플릿은 모두 컴파일 타임에 컴파일러가 코드를 생성하는 것에 관한 것 입니다. 가상 함수는 런타임 시스템에서 호출 할 수있는 기능을 파악 대해 모두 런타임 .

런타임 시스템이 템플릿 화 된 가상 함수를 호출해야한다고 판단되면 컴파일이 모두 완료되고 컴파일러가 더 이상 적절한 인스턴스를 생성 할 수 없습니다. 따라서 가상 멤버 함수 템플릿을 가질 수 없습니다.

그러나, 다형성과 템플릿, 특히 소위 타입 소거 와 결합하여 발생하는 몇 가지 강력하고 흥미로운 기술이 있습니다.


32
나는 이것에 대한 언어 이유를 보지 못하고 구현 이유 만 보았습니다 . vtables는 언어의 일부가 아니라 컴파일러가 언어를 구현하는 표준 방식입니다.
gerardw

16
Virtual functions are all about the run-time system figuring out which function to call at run-time미안하지만 이것은 다소 잘못된 방법이며 혼란 스럽습니다. 그것은 단지 간접적이며, "런타임 계산"과 관련이 없으며, 컴파일 타임 동안 호출 될 함수가 vtable의 n 번째 포인터에 의해 지시 된 것으로 알려져 있습니다. "알아내는 것"은 타입 검사가 있다는 것을 의미하며, 그렇지 않습니다. Once the run-time system figured out it would need to call a templatized virtual function-함수가 가상인지 여부는 컴파일 타임에 알려져 있습니다.
dtech

9
@ddriver : 1. 컴파일러에서를 볼 경우 void f(concr_base& cb, virt_base& vb) { cb.f(); vb.f(); }호출 된 시점에서 어떤 함수가 호출되는지 "알고"에 cb.f()대해 알지 못합니다 vb.f(). 후자는인지되고 실행시 , 런타임 시스템에 의해 . 이것을 "피겨 아웃"이라고 부르든, 이것이 다소 효율적인지 여부는 이러한 사실을 조금도 바꾸지 않습니다.
sbi

9
@ddriver : 2. (멤버) 함수 템플릿의 인스턴스는 (멤버) 함수이므로 그러한 인스턴스에 대한 포인터를 vtable에 넣는 데 전혀 문제가 없습니다. 그러나 어떤 템플릿 인스턴스가 필요한지는 호출자가 컴파일 될 때만 알려진 반면 vtables는 기본 클래스와 파생 클래스가 컴파일 될 때 설정됩니다. 그리고 이것들은 모두 별도로 컴파일됩니다. 더 나쁜 것은 – 런타임에 새로운 파생 클래스를 실행중인 시스템에 링크 할 수 있다는 것입니다 (브라우저가 플러그인을 동적으로로드한다고 생각하십시오). 새로운 파생 클래스가 생성되면 호출자의 소스 코드조차 오랫동안 사라질 수 있습니다.
sbi

9
@ sbi : 왜 내 이름을 기반으로 가정합니까? 제네릭과 템플릿을 혼동하지 않았습니다. Java의 제네릭은 순전히 런타임이라는 것을 알고 있습니다. C ++에서 가상 멤버 함수 템플릿을 사용할 수없는 이유를 철저하게 설명하지는 않았지만 InQsitive는 그 이유를 설명했습니다. 템플릿과 가상 메커니즘을 지나치게 단순화하여 '컴파일 시간'과 '런타임'을 비교하고 "가상 멤버 함수 템플릿을 가질 수 없습니다"라고 결론을 내 렸습니다. "C ++ Templates The Complete Guide"를 참조하는 InQsitive의 답변을 참조했습니다. 나는 그것이 "손을 흔들며"이라고 생각하지 않습니다. 좋은 하루 보내세요
Javanator

133

C ++ 템플릿에서 완전한 가이드 :

멤버 함수 템플릿은 가상으로 선언 할 수 없습니다. 가상 함수 호출 메커니즘의 일반적인 구현은 가상 함수 당 하나의 항목이있는 고정 크기 테이블을 사용하기 때문에 이러한 제한이 적용됩니다. 그러나 멤버 함수 템플릿의 인스턴스화 횟수는 전체 프로그램이 번역 될 때까지 고정되지 않습니다. 따라서 가상 멤버 함수 템플릿을 지원하려면 C ++ 컴파일러 및 링커에서 완전히 새로운 종류의 메커니즘을 지원해야합니다. 반대로, 클래스 템플릿의 일반 멤버는 클래스가 인스턴스화 될 때 숫자가 고정되어 있기 때문에 가상 일 수 있습니다.


8
링크 시간 최적화를 지원하는 오늘날의 C ++ 컴파일러와 링커는 링크 타임에 필요한 vtable과 오프셋을 생성 할 수 있어야한다고 생각합니다. C ++ 2b에서이 기능을 사용할 수 있을까요?
카이 Petzke

33

C ++은 현재 가상 템플릿 멤버 기능을 허용하지 않습니다. 가장 쉬운 이유는 구현이 복잡하기 때문입니다. Rajendra는 지금 할 수없는 이유를 제시하지만 합리적인 표준 변경으로 가능할 수 있습니다. 특히 템플릿 함수의 인스턴스화가 실제로 얼마나 많은지를 계산하고 가상 함수 호출의 위치를 ​​고려하면 vtable을 작성하는 것이 어려워 보입니다. 표준 사람들은 지금 당장 할 일이 많으며 C ++ 1x는 컴파일러 작성자에게도 많은 일입니다.

언제 템플릿 멤버 함수가 필요합니까? 한때 순수한 가상 기본 클래스로 계층 구조를 리팩토링하려고하는 상황을 겪었습니다. 다른 전략을 구현하기에는 좋지 않은 스타일이었습니다. 가상 함수 중 하나의 인수를 숫자 유형으로 변경하고 멤버 함수를 오버로드하는 대신 가상 템플릿 함수를 사용하려고 시도한 모든 하위 클래스의 모든 오버로드를 재정의하는 대신 존재했습니다. .)


5
@ pmr : 함수가 컴파일 될 때조차 존재하지 않은 코드에서 가상 함수가 호출 될 수 있습니다. 컴파일러는 존재하지 않는 코드에 대해 생성 할 (이론적) 가상 템플릿 멤버 함수 인스턴스를 어떻게 결정합니까?
sbi

2
@ sbi : 예, 별도의 컴파일은 큰 문제가 될 것입니다. 나는 C ++ 컴파일러에 대한 전문가가 아니므로 솔루션을 제공 할 수 없습니다. 일반적으로 템플릿 함수와 마찬가지로 모든 컴파일 단위에서 다시 인스턴스화해야합니다. 그래도 문제가 해결되지 않습니까?
pmr

2
@sbi 라이브러리를 동적으로로드하는 것을 언급하는 경우 가상 템플릿 메소드뿐만 아니라 템플릿 클래스 / 함수의 일반적인 문제입니다.
Oak

"C ++은 [...]를 허용하지 않습니다." -표준에 대한 참조를 볼 수 있습니다. (답변이 작성된 시점 또는 8 년 후의 날짜에 관계없이)
아콩 카과

19

가상 기능 테이블

가상 함수 테이블에 대한 배경 지식과 작동 방식 ( source ) 을 시작해 보겠습니다 .

[20.3] 가상 및 비가 상 멤버 함수를 호출하는 방법의 차이점은 무엇입니까?

비가 상 멤버 함수는 정적으로 해결됩니다. 즉, 멤버 함수는 객체에 대한 포인터 (또는 참조)의 유형에 따라 정적으로 (컴파일 타임에) 선택됩니다.

반대로 가상 멤버 기능은 동적으로 (런타임에) 해결됩니다. 즉, 멤버 함수는 해당 객체에 대한 포인터 / 참조 유형이 아니라 객체 유형에 따라 동적으로 (런타임에) 선택됩니다. 이것을 "동적 바인딩"이라고합니다. 대부분의 컴파일러는 다음 기술의 변형을 사용합니다. 개체에 가상 함수가 하나 이상 있으면 "가상 포인터"또는 "v- 포인터"라는 개체에 숨겨진 포인터를 놓습니다. 이 v- 포인터는 "virtual-table"또는 "v-table"이라는 글로벌 테이블을 가리 킵니다.

컴파일러는 가상 함수가 하나 이상있는 각 클래스에 대해 v-table을 만듭니다. 예를 들어 Circle 클래스에 draw () 및 move () 및 resize ()에 대한 가상 함수가있는 경우 gazillion Circle 객체가 있고 v-pointer가 있더라도 Circle 클래스와 연결된 v-table이 정확히 하나가됩니다. 각 Circle 객체는 Circle v-table을 가리 킵니다. v-table 자체에는 클래스의 각 가상 함수에 대한 포인터가 있습니다. 예를 들어 Circle v-table에는 Circle :: draw ()에 대한 포인터, Circle :: move ()에 대한 포인터 및 Circle :: resize ()에 대한 포인터가 있습니다.

가상 함수를 디스패치하는 동안 런타임 시스템은 객체의 v 포인터를 따라 클래스의 v- 테이블로 이동 한 다음 v- 테이블의 해당 슬롯을 따라 메소드 코드로 이동합니다.

위 기술의 공간 비용 오버 헤드는 명목상입니다. 즉 객체 당 추가 포인터 (동적 바인딩이 필요한 객체에만 해당)와 메서드 당 추가 포인터 (가상 메서드에만 해당)입니다. 시간이 걸리는 오버 헤드도 상당히 명목입니다. 일반 함수 호출과 비교할 때 가상 함수 호출에는 두 개의 추가 페치가 필요합니다 (하나는 v- 포인터의 값을 얻기 위해, 하나는 메소드의 주소를 얻기 위해). 컴파일러는 포인터 유형에 따라 컴파일 타임에만 비가 상 함수를 해석하므로이 비가 상 함수에서는이 런타임 활동이 발생하지 않습니다.


내 문제 또는 내가 어떻게 왔는지

다른 유형의 큐브 (일부는 픽셀, 일부는 이미지 등)에 대해 다르게 구현되는 템플릿으로 최적화 된로드 함수가있는 큐브 파일 기본 클래스에 이와 같은 것을 사용하려고합니다.

일부 코드 :

virtual void  LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

내가 원하는 것은 아니지만 가상 템플릿 콤보로 인해 컴파일되지 않습니다.

template<class T>
    virtual void  LoadCube(UtpBipCube<T> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

결국 템플릿 선언을 클래스 수준으로 옮겼습니다 . 이 솔루션은 프로그램이 읽기 전에 읽을 특정 유형의 데이터에 대해 프로그램이 알도록 강요했을 것입니다.

해결책

경고, 이것은 매우 예쁘지는 않지만 반복적 인 실행 코드를 제거 할 수있게했습니다.

1) 기본 수업

virtual void  LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

2) 어린이 수업

void  LoadCube(UtpBipCube<float> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

template<class T>
void  LoadAnyCube(UtpBipCube<T> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1);

LoadAnyCube는 기본 클래스에서 선언되지 않습니다.


다음은 해결 방법 이있는 또 다른 스택 오버플로 답변입니다 . 가상 템플릿 멤버 해결 방법이 필요합니다 .


1
나는 같은 상황과 대중 클래스의 상속 구조를 만났다. 매크로가 도움이되었습니다.
ZFY

16

Window 7에서 MinGW G ++ 3.4.5를 사용하여 다음 코드를 컴파일하고 올바르게 실행할 수 있습니다.

#include <iostream>
#include <string>

using namespace std;

template <typename T>
class A{
public:
    virtual void func1(const T& p)
    {
        cout<<"A:"<<p<<endl;
    }
};

template <typename T>
class B
: public A<T>
{
public:
    virtual void func1(const T& p)
    {
        cout<<"A<--B:"<<p<<endl;
    }
};

int main(int argc, char** argv)
{
    A<string> a;
    B<int> b;
    B<string> c;

    A<string>* p = &a;
    p->func1("A<string> a");
    p = dynamic_cast<A<string>*>(&c);
    p->func1("B<string> c");
    B<int>* q = &b;
    q->func1(3);
}

출력은 다음과 같습니다.

A:A<string> a
A<--B:B<string> c
A<--B:3

그리고 나중에 새로운 클래스 X를 추가했습니다.

class X
{
public:
    template <typename T>
    virtual void func2(const T& p)
    {
        cout<<"C:"<<p<<endl;
    }
};

main ()에서 다음과 같이 클래스 X를 사용하려고했을 때 :

X x;
x.func2<string>("X x");

g ++는 다음 오류를보고합니다.

vtempl.cpp:34: error: invalid use of `virtual' in template declaration of `virtu
al void X::func2(const T&)'

따라서 다음이 분명합니다.

  • 가상 멤버 함수는 클래스 템플릿에서 사용할 수 있습니다. 컴파일러가 vtable을 생성하는 것은 쉽다
  • 클래스 템플릿 멤버 함수를 가상으로 정의하는 것은 불가능합니다.보다시피 함수 시그니처를 결정하고 vtable 항목을 할당하는 것은 어렵습니다.

19
클래스 템플릿에는 가상 멤버 함수가있을 수 있습니다. 멤버 함수는 멤버 함수 템플릿과 가상 멤버 함수가 될 수 없습니다.
제임스 맥닐리 스

1
실제로 gcc 4.4.3에서는 실패합니다. 내 시스템에서 확실히 Ubuntu 10.04
blueskin 5

3
이것은 질문과 완전히 다릅니다. 여기에 전체 기본 클래스가 템플릿입니다. 나는 전에 이런 종류의 것을 컴파일했습니다. 이것은 Visual Studio 2010에서도 컴파일 될 것입니다
ds-bos-msk

14

그들은 할 수 없습니다. 그러나:

template<typename T>
class Foo {
public:
  template<typename P>
  void f(const P& p) {
    ((T*)this)->f<P>(p);
  }
};

class Bar : public Foo<Bar> {
public:
  template<typename P>
  void f(const P& p) {
    std::cout << p << std::endl;
  }
};

int main() {
  Bar bar;

  Bar *pbar = &bar;
  pbar -> f(1);

  Foo<Bar> *pfoo = &bar;
  pfoo -> f(1);
};

당신이하고 싶은 모든 것이 공통 인터페이스를 가지고 서브 클래스에 대한 구현을 연기하는 것이라면 거의 같은 효과가 있습니다.


3
궁금한 사람이 있으면 CRTP라고합니다.
마이클 최

1
그러나 이것은 클래스 계층 구조를 가지고 기본 클래스에 대한 포인터의 가상 메소드를 호출 할 수있는 경우에는 도움이되지 않습니다. 귀하 Foo으로 포인터가 자격 Foo<Bar>그것이 가리 수 없습니다, Foo<Barf>또는 Foo<XXX>.
Kai Petzke

@ KaiPetzke : 제한없는 포인터를 만들 수 없습니다. 그러나 구체적인 유형을 알 필요가없는 코드를 템플릿으로 만들 수 있습니다.
Tom

8

아니요, 템플릿 멤버 함수는 가상 일 수 없습니다.


9
나의 호기심은 : 왜? 컴파일러는 그렇게 할 때 어떤 문제에 직면합니까?
WannaBeGeek

1
범위에 선언이 필요합니다 (적어도 유형을 올바르게 얻으려면). 표준 (및 언어)에 따라 사용하는 식별자의 범위 내에 선언이 있어야합니다.
dirkgently

4

다른 답변에서 제안 된 템플릿 기능은 외관이며 실질적인 이점을 제공하지 않습니다.

  • 템플릿 함수는 다른 유형을 사용하여 한 번만 코드를 작성하는 데 유용합니다.
  • 가상 함수는 다른 클래스에 대한 공통 인터페이스를 갖는 데 유용합니다.

이 언어는 가상 템플릿 기능을 허용하지 않지만 해결 방법을 통해 각 클래스에 대해 하나의 템플릿 구현과 가상 공통 인터페이스를 모두 사용할 수 있습니다.

그러나 각 템플릿 유형 조합에 더미 가상 래퍼 함수를 ​​정의해야합니다.

#include <memory>
#include <iostream>
#include <iomanip>

//---------------------------------------------
// Abstract class with virtual functions
class Geometry {
public:
    virtual void getArea(float &area) = 0;
    virtual void getArea(long double &area) = 0;
};

//---------------------------------------------
// Square
class Square : public Geometry {
public:
    float size {1};

    // virtual wrapper functions call template function for square
    virtual void getArea(float &area) { getAreaT(area); }
    virtual void getArea(long double &area) { getAreaT(area); }

private:
    // Template function for squares
    template <typename T>
    void getAreaT(T &area) {
        area = static_cast<T>(size * size);
    }
};

//---------------------------------------------
// Circle
class Circle : public Geometry  {
public:
    float radius {1};

    // virtual wrapper functions call template function for circle
    virtual void getArea(float &area) { getAreaT(area); }
    virtual void getArea(long double &area) { getAreaT(area); }

private:
    // Template function for Circles
    template <typename T>
    void getAreaT(T &area) {
        area = static_cast<T>(radius * radius * 3.1415926535897932385L);
    }
};


//---------------------------------------------
// Main
int main()
{
    // get area of square using template based function T=float
    std::unique_ptr<Geometry> geometry = std::make_unique<Square>();
    float areaSquare;
    geometry->getArea(areaSquare);

    // get area of circle using template based function T=long double
    geometry = std::make_unique<Circle>();
    long double areaCircle;
    geometry->getArea(areaCircle);

    std::cout << std::setprecision(20) << "Square area is " << areaSquare << ", Circle area is " << areaCircle << std::endl;
    return 0;
}

산출:

사각형 영역은 1, 원형 영역은 3.1415926535897932385

여기 사용해보십시오


3

질문의 두 번째 부분에 대답하려면 :

이들이 가상 일 수 있다면, 그러한 기능을 사용하는 시나리오의 예는 무엇입니까?

이것은 불합리한 일이 아닙니다. 예를 들어, 모든 메소드가 가상 인 Java는 일반 메소드에 문제가 없습니다.

가상 함수 템플릿을 원하는 C ++의 한 예는 일반 반복자를 허용하는 멤버 함수입니다. 또는 일반 함수 객체를 허용하는 멤버 함수입니다.

이 문제에 대한 해결책은 boost :: any_range 및 boost :: function과 함께 유형 삭제를 사용하여 함수를 템플릿으로 만들 필요없이 일반 반복자 또는 functor를 허용 할 수 있습니다.


6
Java 제네릭은 캐스팅을위한 구문 설탕입니다. 템플릿과 동일하지 않습니다.
Brice M. Dempsey 2016 년

2
@ BriceM.Dempsey : 당신은 캐스팅이 자바가 제네릭을 구현하는 방법이라고 말할 수 있습니다.
einpoklum

2

템플릿 방법의 유형 집합이 미리 알려진 경우 '가상 템플릿 방법'에 대한 해결 방법이 있습니다.

아이디어를 보여주기 위해 아래 예에서는 두 가지 유형 만 사용됩니다 ( intdouble).

거기에서 '가상'템플릿 메소드 ( Base::Method)는 해당 가상 메소드 (중 하나 Base::VMethod)를 호출하고, 차례로 템플릿 메소드 구현 ( Impl::TMethod)을 호출합니다 .

하나는 템플릿 방법을 구현해야 TMethod파생 구현에 ( AImpl, BImpl) 사용을 Derived<*Impl>.

class Base
{
public:
    virtual ~Base()
    {
    }

    template <typename T>
    T Method(T t)
    {
        return VMethod(t);
    }

private:
    virtual int VMethod(int t) = 0;
    virtual double VMethod(double t) = 0;
};

template <class Impl>
class Derived : public Impl
{
public:
    template <class... TArgs>
    Derived(TArgs&&... args)
        : Impl(std::forward<TArgs>(args)...)
    {
    }

private:
    int VMethod(int t) final
    {
        return Impl::TMethod(t);
    }

    double VMethod(double t) final
    {
        return Impl::TMethod(t);
    }
};

class AImpl : public Base
{
protected:
    AImpl(int p)
        : i(p)
    {
    }

    template <typename T>
    T TMethod(T t)
    {
        return t - i;
    }

private:
    int i;
};

using A = Derived<AImpl>;

class BImpl : public Base
{
protected:
    BImpl(int p)
        : i(p)
    {
    }

    template <typename T>
    T TMethod(T t)
    {
        return t + i;
    }

private:
    int i;
};

using B = Derived<BImpl>;

int main(int argc, const char* argv[])
{
    A a(1);
    B b(1);
    Base* base = nullptr;

    base = &a;
    std::cout << base->Method(1) << std::endl;
    std::cout << base->Method(2.0) << std::endl;

    base = &b;
    std::cout << base->Method(1) << std::endl;
    std::cout << base->Method(2.0) << std::endl;
}

산출:

0
1
2
3

NB : Base::Method실제로 실제 코드에 대한 잉여입니다 ( VMethod공개 및 직접 사용할 수 있음). 실제 '가상'템플릿 방법처럼 보이도록 추가했습니다.


직장에서 문제를 해결 하면서이 솔루션을 생각해 냈습니다. 위의 Mark Essel과 비슷하지만 더 잘 구현되고 설명되기를 바랍니다.
sad1raf

나는 이것을 코드 난독 화로 이미 인정했지만 Base, 지금까지 구현 된 것과 호환되지 않는 인수 유형으로 템플릿 함수를 호출해야 할 때마다 원래 클래스 를 수정해야한다는 사실을 여전히 극복하지 못했습니다 . 이러한 필요성을 피하는 것은 템플릿의 의도입니다.
Aconcagua

Essels 접근 방식은 완전히 다릅니다. 다른 템플릿 인스턴스화를 허용하는 일반 가상 함수-파생 클래스의 최종 템플릿 함수는 코드 중복을 피하는 역할 만하며 기본 클래스에는 카운터 부분이 없습니다 ...
Aconcagua

2

많은 사람들이 대답 한 오래된 질문이지만 간결한 방법은 게시 된 다른 방법과 크게 다르지 않지만 클래스 선언의 중복을 완화하는 데 도움이되는 작은 매크로를 사용하는 것입니다.

// abstract.h

// Simply define the types that each concrete class will use
#define IMPL_RENDER() \
    void render(int a, char *b) override { render_internal<char>(a, b); }   \
    void render(int a, short *b) override { render_internal<short>(a, b); } \
    // ...

class Renderable
{
public:
    // Then, once for each on the abstract
    virtual void render(int a, char *a) = 0;
    virtual void render(int a, short *b) = 0;
    // ...
};

이제 서브 클래스를 구현하려면 :

class Box : public Renderable
{
public:
    IMPL_RENDER() // Builds the functions we want

private:
    template<typename T>
    void render_internal(int a, T *b); // One spot for our logic
};

여기서 지원되는 이점은 새로 지원되는 유형을 추가 할 때 추상 헤더에서 모두 수행 할 수 있으며 여러 소스 / 헤더 파일에서이를 수정할 수 없다는 것입니다.


0

최소한 gcc 5.4에서 가상 함수는 템플릿 멤버 일 수 있지만 템플릿 자체 여야합니다.

#include <iostream>
#include <string>
class first {
protected:
    virtual std::string  a1() { return "a1"; }
    virtual std::string  mixt() { return a1(); }
};

class last {
protected:
    virtual std::string a2() { return "a2"; }
};

template<class T>  class mix: first , T {
    public:
    virtual std::string mixt() override;
};

template<class T> std::string mix<T>::mixt() {
   return a1()+" before "+T::a2();
}

class mix2: public mix<last>  {
    virtual std::string a1() override { return "mix"; }
};

int main() {
    std::cout << mix2().mixt();
    return 0;
}

출력

mix before a2
Process finished with exit code 0

0

이 시도:

classeder.h에 작성하십시오.

template <typename T>
class Example{
public:
    T c_value;

    Example(){}

    T Set(T variable)
    {
          return variable;
    }

    virtual Example VirtualFunc(Example paraM)
    {
         return paraM.Set(c_value);
    }

이 작업을 수행하는 경우 main.cpp에서이 코드를 작성하십시오.

#include <iostream>
#include <classeder.h>

int main()
{
     Example exmpl;
     exmpl.c_value = "Hello, world!";
     std::cout << exmpl.VirtualFunc(exmpl);
     return 0;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.