C ++ 클래스 멤버 함수 템플릿은 가상이 될 수 없다고 들었습니다. 이것이 사실입니까?
이들이 가상 일 수 있다면, 그러한 기능을 사용하는 시나리오의 예는 무엇입니까?
C ++ 클래스 멤버 함수 템플릿은 가상이 될 수 없다고 들었습니다. 이것이 사실입니까?
이들이 가상 일 수 있다면, 그러한 기능을 사용하는 시나리오의 예는 무엇입니까?
답변:
템플릿은 모두 컴파일 타임에 컴파일러가 코드를 생성하는 것에 관한 것 입니다. 가상 함수는 런타임 시스템에서 호출 할 수있는 기능을 파악 대해 모두 런타임 .
런타임 시스템이 템플릿 화 된 가상 함수를 호출해야한다고 판단되면 컴파일이 모두 완료되고 컴파일러가 더 이상 적절한 인스턴스를 생성 할 수 없습니다. 따라서 가상 멤버 함수 템플릿을 가질 수 없습니다.
그러나, 다형성과 템플릿, 특히 소위 타입 소거 와 결합하여 발생하는 몇 가지 강력하고 흥미로운 기술이 있습니다.
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
-함수가 가상인지 여부는 컴파일 타임에 알려져 있습니다.
void f(concr_base& cb, virt_base& vb) { cb.f(); vb.f(); }
호출 된 시점에서 어떤 함수가 호출되는지 "알고"에 cb.f()
대해 알지 못합니다 vb.f()
. 후자는인지되고 실행시 , 런타임 시스템에 의해 . 이것을 "피겨 아웃"이라고 부르든, 이것이 다소 효율적인지 여부는 이러한 사실을 조금도 바꾸지 않습니다.
C ++ 템플릿에서 완전한 가이드 :
멤버 함수 템플릿은 가상으로 선언 할 수 없습니다. 가상 함수 호출 메커니즘의 일반적인 구현은 가상 함수 당 하나의 항목이있는 고정 크기 테이블을 사용하기 때문에 이러한 제한이 적용됩니다. 그러나 멤버 함수 템플릿의 인스턴스화 횟수는 전체 프로그램이 번역 될 때까지 고정되지 않습니다. 따라서 가상 멤버 함수 템플릿을 지원하려면 C ++ 컴파일러 및 링커에서 완전히 새로운 종류의 메커니즘을 지원해야합니다. 반대로, 클래스 템플릿의 일반 멤버는 클래스가 인스턴스화 될 때 숫자가 고정되어 있기 때문에 가상 일 수 있습니다.
C ++은 현재 가상 템플릿 멤버 기능을 허용하지 않습니다. 가장 쉬운 이유는 구현이 복잡하기 때문입니다. Rajendra는 지금 할 수없는 이유를 제시하지만 합리적인 표준 변경으로 가능할 수 있습니다. 특히 템플릿 함수의 인스턴스화가 실제로 얼마나 많은지를 계산하고 가상 함수 호출의 위치를 고려하면 vtable을 작성하는 것이 어려워 보입니다. 표준 사람들은 지금 당장 할 일이 많으며 C ++ 1x는 컴파일러 작성자에게도 많은 일입니다.
언제 템플릿 멤버 함수가 필요합니까? 한때 순수한 가상 기본 클래스로 계층 구조를 리팩토링하려고하는 상황을 겪었습니다. 다른 전략을 구현하기에는 좋지 않은 스타일이었습니다. 가상 함수 중 하나의 인수를 숫자 유형으로 변경하고 멤버 함수를 오버로드하는 대신 가상 템플릿 함수를 사용하려고 시도한 모든 하위 클래스의 모든 오버로드를 재정의하는 대신 존재했습니다. .)
가상 함수 테이블에 대한 배경 지식과 작동 방식 ( 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는 기본 클래스에서 선언되지 않습니다.
다음은 해결 방법 이있는 또 다른 스택 오버플로 답변입니다 . 가상 템플릿 멤버 해결 방법이 필요합니다 .
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&)'
따라서 다음이 분명합니다.
그들은 할 수 없습니다. 그러나:
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);
};
당신이하고 싶은 모든 것이 공통 인터페이스를 가지고 서브 클래스에 대한 구현을 연기하는 것이라면 거의 같은 효과가 있습니다.
Foo
으로 포인터가 자격 Foo<Bar>
그것이 가리 수 없습니다, Foo<Barf>
또는 Foo<XXX>
.
아니요, 템플릿 멤버 함수는 가상 일 수 없습니다.
다른 답변에서 제안 된 템플릿 기능은 외관이며 실질적인 이점을 제공하지 않습니다.
이 언어는 가상 템플릿 기능을 허용하지 않지만 해결 방법을 통해 각 클래스에 대해 하나의 템플릿 구현과 가상 공통 인터페이스를 모두 사용할 수 있습니다.
그러나 각 템플릿 유형 조합에 더미 가상 래퍼 함수를 정의해야합니다.
#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
여기 사용해보십시오
질문의 두 번째 부분에 대답하려면 :
이들이 가상 일 수 있다면, 그러한 기능을 사용하는 시나리오의 예는 무엇입니까?
이것은 불합리한 일이 아닙니다. 예를 들어, 모든 메소드가 가상 인 Java는 일반 메소드에 문제가 없습니다.
가상 함수 템플릿을 원하는 C ++의 한 예는 일반 반복자를 허용하는 멤버 함수입니다. 또는 일반 함수 객체를 허용하는 멤버 함수입니다.
이 문제에 대한 해결책은 boost :: any_range 및 boost :: function과 함께 유형 삭제를 사용하여 함수를 템플릿으로 만들 필요없이 일반 반복자 또는 functor를 허용 할 수 있습니다.
템플릿 방법의 유형 집합이 미리 알려진 경우 '가상 템플릿 방법'에 대한 해결 방법이 있습니다.
아이디어를 보여주기 위해 아래 예에서는 두 가지 유형 만 사용됩니다 ( int
및 double
).
거기에서 '가상'템플릿 메소드 ( 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
공개 및 직접 사용할 수 있음). 실제 '가상'템플릿 방법처럼 보이도록 추가했습니다.
Base
, 지금까지 구현 된 것과 호환되지 않는 인수 유형으로 템플릿 함수를 호출해야 할 때마다 원래 클래스 를 수정해야한다는 사실을 여전히 극복하지 못했습니다 . 이러한 필요성을 피하는 것은 템플릿의 의도입니다.
많은 사람들이 대답 한 오래된 질문이지만 간결한 방법은 게시 된 다른 방법과 크게 다르지 않지만 클래스 선언의 중복을 완화하는 데 도움이되는 작은 매크로를 사용하는 것입니다.
// 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
};
여기서 지원되는 이점은 새로 지원되는 유형을 추가 할 때 추상 헤더에서 모두 수행 할 수 있으며 여러 소스 / 헤더 파일에서이를 수정할 수 없다는 것입니다.
최소한 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
이 시도:
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;
}