C ++ sınıf üyesi işlev şablonlarının sanal olamayacağını duydum. Bu doğru mu?
Sanal olabilirlerse, böyle bir işlevi kullanacağı bir senaryo örneği nedir?
C ++ sınıf üyesi işlev şablonlarının sanal olamayacağını duydum. Bu doğru mu?
Sanal olabilirlerse, böyle bir işlevi kullanacağı bir senaryo örneği nedir?
Yanıtlar:
Şablonlar derleyicinin derleme zamanında kod üretmesiyle ilgilidir . Sanal fonksiyonlar çalışma zamanı sistemi de aramaya hangi işlevi bulmakla ilgili olan çalışma zamanı .
Çalışma zamanı sistemi geçici bir sanal işlev çağırması gerektiğini anladıktan sonra, derleme tamamlanır ve derleyici artık uygun örneği oluşturamaz. Bu nedenle, sanal üye işlev şablonlarınız olamaz.
Bununla birlikte, polimorfizm ve şablonların, özellikle de tip silme olarak birleştirilmesinden kaynaklanan birkaç güçlü ve ilginç teknik vardır .
Virtual functions are all about the run-time system figuring out which function to call at run-time
- üzgünüm ama bu oldukça yanlış bir yol ve oldukça kafa karıştırıcı. Sadece dolaylıdır ve bir "çalışma zamanı çözme" işlemi söz konusu değildir, derleme zamanında çağrılacak işlevin, vtable'daki n. İşaretçi tarafından işaret edilen işlev olduğu bilinmektedir. "Anlaşılması", tip kontrolleri olduğu anlamına gelir ve durum böyle değildir. Once the run-time system figured out it would need to call a templatized virtual function
- derleme zamanında işlevin sanal olup olmadığı bilinir.
void f(concr_base& cb, virt_base& vb) { cb.f(); vb.f(); }
, hangi fonksiyonun çağrıldığı noktada çağrıldığını "bilir" cb.f()
ve bunu bilmez vb.f()
. İkincisi çalışma zamanında , çalışma zamanı sistemi tarafından bulunmalıdır . Buna "çözme" demek isteyip istemediğinizi ve bunun az ya da çok etkili olup olmadığını, bu gerçekleri biraz değiştirmez.
C ++ Şablonlarından Komple Kılavuz:
Üye işlev şablonları sanal olarak bildirilemez. Bu kısıtlama, sanal işlev çağrısı mekanizmasının olağan uygulaması, sanal işlev başına bir giriş içeren sabit boyutlu bir tablo kullandığından uygulanır. Ancak, üye işlev şablonunun örneklerinin sayısı, tüm program çevrilene kadar sabitlenmez. Bu nedenle, sanal üye işlev şablonlarını desteklemek için C ++ derleyicilerinde ve bağlayıcılarında yepyeni bir mekanizma için destek gerekir. Buna karşılık, sınıf şablonlarının sıradan üyeleri sanal olabilir, çünkü sınıf somutlaştırıldığında sayıları sabittir
C ++ şu anda sanal şablon üye işlevlerine izin vermiyor. En olası neden, onu uygulamanın karmaşıklığıdır. Rajendra şu anda neden yapılamamasının iyi bir nedenini veriyor, ancak standardın makul değişiklikleri ile mümkün olabilir. Özellikle sanal işlev çağrısının yerini düşünürseniz, şablonlanmış bir işlevin kaç tane örneğinin gerçekte var olduğunu ve vtable'ı oluşturmak zor görünüyor. Standartlar insanların şu anda yapacak başka şeyleri var ve C ++ 1x derleyici yazarları için de çok iş.
Geçici bir üye işlevine ne zaman ihtiyaç duyarsınız? Bir zamanlar saf bir sanal temel sınıfla bir hiyerarşiyi yeniden düzenlemeye çalıştığım bir durumla karşılaştım. Farklı stratejileri uygulamak için zayıf bir tarzdı. Sanal işlevlerden birinin bağımsız değişkenini sayısal bir türe değiştirmek ve üye işlevini aşırı yüklemek yerine, sanal şablon işlevlerini kullanmaya çalıştığım tüm alt sınıflardaki her aşırı yükü geçersiz kılmak yerine (ve bunların var olmadığını öğrenmek zorunda kaldım) .)
Sanal işlev tabloları ve bunların nasıl çalıştığı hakkında bazı bilgilerle başlayalım ( kaynak ):
[20.3] Sanal ve sanal olmayan üye işlevlerinin çağrılması arasındaki fark nedir?
Sanal olmayan üye işlevleri statik olarak çözümlenir. Yani, üye işlevi, nesneye yönelik işaretçinin (veya başvurunun) türüne göre statik olarak (derleme zamanında) seçilir.
Buna karşılık, sanal üye işlevleri dinamik olarak (çalışma zamanında) çözülür. Yani üye işlevi, o nesneye işaretçi / referansın türüne değil, nesnenin türüne göre dinamik olarak (çalışma zamanında) seçilir. Buna "dinamik ciltleme" denir. Çoğu derleyici aşağıdaki tekniğin bazı varyantlarını kullanır: nesnenin bir veya daha fazla sanal işlevi varsa, derleyici nesneye "sanal-işaretçi" veya "v-işaretçisi" adı verilen gizli bir işaretçi koyar. Bu v-işaretçisi "sanal tablo" veya "v-tablosu" adlı genel bir tabloyu gösterir.
Derleyici, en az bir sanal işlevi olan her sınıf için bir v-tablosu oluşturur. Örneğin, Circle sınıfı, draw () ve move () ve resize () için sanal işlevlere sahipse, bir gazillion Circle nesnesi olsa bile Circle sınıfıyla ilişkilendirilmiş tam olarak bir v-tablosu ve bu Circle nesnelerinin her biri Circle v-tablosuna işaret eder. V-tablosunun kendisi sınıftaki sanal işlevlerin her birine işaret eder. Örneğin, Circle v-tablosunda üç işaretçi bulunur: Circle :: draw () öğesine bir işaretçi, Circle :: move () öğesine bir işaretçi ve Circle :: resize () öğesine bir işaretçi.
Bir sanal işlevin gönderilmesi sırasında, çalışma zamanı sistemi nesnenin v-işaretçisini sınıfın v-tablosuna, ardından v-tablosundaki yöntem yuvasına uygun yuvayı takip eder.
Yukarıdaki tekniğin alan maliyeti ek yükü nominaldir: nesne başına fazladan bir işaretçi (ancak yalnızca dinamik bağlama yapması gereken nesneler için) ve yöntem başına fazladan bir işaretçi (ancak yalnızca sanal yöntemler için). Zaman maliyeti ek yükü de oldukça nominaldir: normal bir işlev çağrısına kıyasla, sanal bir işlev çağrısı iki ekstra getirme gerektirir (biri v-işaretçisinin değerini almak için bir, yöntemin adresini almak için bir saniye). Derleyici sanal olmayan işlevleri yalnızca işaretçinin türüne göre derleme zamanında çözdüğü için bu çalışma zamanı etkinliğinin hiçbiri sanal olmayan işlevlerde gerçekleşmez.
Farklı tür küpler (bazıları piksel, bazıları görüntü, vb.) İçin farklı uygulanacak templated optimize yük fonksiyonları ile bir küp dosya taban sınıfı için şimdi böyle bir şey kullanmaya çalışıyorum.
Bazı kodlar:
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;
Ne olmasını isterdim, ancak sanal templated combo nedeniyle derlenmeyecek:
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;
Şablon bildirimini sınıf seviyesine taşıdım . Bu çözüm, programları okumadan önce okuyacakları belirli veri türlerini öğrenmeye zorlardı, bu kabul edilemez.
uyarı, bu çok hoş değil ama tekrarlayan yürütme kodunu kaldırmama izin verdi
1) temel sınıfta
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) ve çocuk sınıflarında
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 öğesinin temel sınıfta bildirilmediğini unutmayın.
İşte geçici çözüm içeren başka bir yığın taşması yanıtı: sanal şablon üyesi geçici çözümüne ihtiyacınız var .
Aşağıdaki kod, Pencere 7'de MinGW G ++ 3.4.5 kullanılarak derlenebilir ve düzgün çalışır:
#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);
}
ve çıktı:
A:A<string> a
A<--B:B<string> c
A<--B:3
Ve sonra yeni bir X sınıfı ekledim:
class X
{
public:
template <typename T>
virtual void func2(const T& p)
{
cout<<"C:"<<p<<endl;
}
};
Main () sınıfı X kullanmaya şu şekilde çalıştığımda:
X x;
x.func2<string>("X x");
g ++ aşağıdaki hatayı bildirin:
vtempl.cpp:34: error: invalid use of `virtual' in template declaration of `virtu
al void X::func2(const T&)'
Yani belli ki:
Hayır yapamazlar. Fakat:
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);
};
tek yapmanız gereken ortak bir arayüze sahip olmak ve uygulamayı alt sınıflara ertelemekse aynı etkiye sahiptir.
Foo
olarak işaretçi nitelikli Foo<Bar>
bir işaret edemez Foo<Barf>
ya Foo<XXX>
.
Hayır, şablon üyesi işlevleri sanal olamaz.
Diğer cevaplarda önerilen şablon fonksiyonu bir cephedir ve herhangi bir pratik fayda sağlamaz.
Dil, sanal şablon işlevlerine izin vermez, ancak geçici bir çözümle, örneğin her sınıf için bir şablon uygulaması ve bir sanal ortak arabirime sahip olmak mümkündür.
Bununla birlikte, her bir şablon türü kombinasyonu için bir kukla sanal sarma işlevi tanımlamak gerekir:
#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;
}
Çıktı:
Kare alan 1, Daire alan 3.1415926535897932385
Burada deneyin
Sorunun ikinci bölümünü cevaplamak için:
Sanal olabilirlerse, böyle bir işlevi kullanacağı bir senaryo örneği nedir?
Bu yapmak mantıksız bir şey değil. Örneğin, Java'nın (her yöntemin sanal olduğu) genel yöntemlerle bir sorunu yoktur.
C ++ 'da sanal işlev şablonu istemenin bir örneği, genel bir yineleyiciyi kabul eden bir üye işlevidir. Veya genel işlev nesnesini kabul eden bir üye işlev.
Bu sorunun çözümü, fonksiyonunuzu bir şablon haline getirmeye gerek kalmadan genel bir yineleyici veya functor kabul etmenizi sağlayacak boost :: any_range ve boost :: fonksiyonuyla tür silme yöntemini kullanmaktır.
Şablon yöntemi için tür kümesi önceden biliniyorsa, 'sanal şablon yöntemi' için bir geçici çözüm vardır.
Fikri göstermek için, aşağıdaki örnekte sadece iki tür kullanılmaktadır ( int
ve double
).
Burada, bir 'sanal' şablon yöntemi ( Base::Method
) karşılık gelen sanal yöntemi (bunlardan biri Base::VMethod
) çağırır, bu da şablon yöntemi uygulamasını ( Impl::TMethod
) çağırır .
Şablon türünün TMethod
sadece türetilmiş uygulamalarda ( AImpl
, BImpl
) uygulanması ve kullanılması gerekir 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;
}
Çıktı:
0
1
2
3
Not:
Base::Method
aslında gerçek kod fazlasıdır ( VMethod
kamuya açık yapılabilir ve doğrudan kullanılabilir). Ekledim, böylece gerçek bir 'sanal' şablon yöntemi gibi görünüyor.
Base
, şimdiye kadar uygulanmış olanlarla uyumlu olmayan bir argüman türüyle bir şablon işlevini çağırmanız gerektiğinde orijinal sınıfı değiştirmek zorunda kalmazsınız . Bu zorunluluktan kaçınmak şablonların
Birçok kişi tarafından cevaplanan daha eski bir soru, gönderilen diğerlerinden çok farklı olmayan kısa ve öz bir yöntem olduğuna inanırken, sınıf bildirimlerinin çoğaltılmasını kolaylaştırmak için küçük bir makro kullanmaktır.
// 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;
// ...
};
Şimdi, alt sınıfımızı uygulamak için:
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
};
Buradaki fayda, yeni desteklenen bir tür eklerken, tümünün soyut üstbilgiden yapılabileceği ve muhtemelen birden çok kaynak / başlık dosyasında düzeltilebileceğidir.
En azından gcc 5.4 ile sanal işlevler şablon üyeleri olabilir, ancak şablonların kendileri olmalıdır.
#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;
}
çıktılar
mix before a2
Process finished with exit code 0
Bunu dene:
Classeder.h yazınız:
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);
}
Bununla çalışıyorsanız, main.cpp'de bu kodu yazmak için kontrol edin:
#include <iostream>
#include <classeder.h>
int main()
{
Example exmpl;
exmpl.c_value = "Hello, world!";
std::cout << exmpl.VirtualFunc(exmpl);
return 0;
}