Programing

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

lottogame 2020. 3. 20. 08:14
반응형

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


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

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


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

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

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


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

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


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

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


가상 기능 테이블

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

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

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

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

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

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

위 기술의 공간 비용 오버 헤드는 명목상입니다. 즉 객체 당 추가 포인터 (동적 바인딩이 필요한 객체에만 해당)와 메소드 당 추가 포인터 (가상 메서드에만 해당)입니다. 시간이 걸리는 오버 헤드도 상당히 명목입니다. 일반 함수 호출과 비교할 때 가상 함수 호출에는 두 개의 추가 페치가 필요합니다 (하나는 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는 기본 클래스에서 선언되지 않습니다.


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


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 항목을 할당하는 것은 어렵습니다.

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

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);
};

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


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


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

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

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

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

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


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

아이디어를 보여주기 위해 아래 예에서는 두 가지 유형 만 사용됩니다 ( 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공개 및 직접 사용할 수 있음). 실제 '가상'템플릿 방법처럼 보이도록 추가했습니다.


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

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

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

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

#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

여기 사용해보십시오


적어도 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

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

// 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
};

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

참고 URL : https://stackoverflow.com/questions/2354210/can-a-class-member-function-template-be-virtual

반응형