Saya telah mendengar bahwa templat fungsi anggota kelas C ++ tidak bisa virtual. Apakah ini benar?
Jika mereka bisa virtual, apa contoh skenario di mana seseorang akan menggunakan fungsi seperti itu?
Saya telah mendengar bahwa templat fungsi anggota kelas C ++ tidak bisa virtual. Apakah ini benar?
Jika mereka bisa virtual, apa contoh skenario di mana seseorang akan menggunakan fungsi seperti itu?
Jawaban:
Template adalah semua tentang kode penghasil kompiler pada waktu kompilasi . Fungsi virtual adalah semua tentang sistem run-time yang mencari tahu fungsi mana yang harus dipanggil pada saat run-time .
Setelah sistem run-time menemukan itu perlu memanggil fungsi virtual templatized, kompilasi semua dilakukan dan kompiler tidak dapat menghasilkan contoh yang sesuai lagi. Karenanya Anda tidak dapat memiliki templat fungsi anggota virtual.
Namun, ada beberapa teknik yang kuat dan menarik yang berasal dari menggabungkan polimorfisme dan template, terutama yang disebut penghapusan tipe .
Virtual functions are all about the run-time system figuring out which function to call at run-time
- maaf tapi ini cara yang agak salah, dan cukup membingungkan. Itu hanya tipuan, dan tidak ada "runtime figuring out" yang terlibat, diketahui selama waktu kompilasi bahwa fungsi yang akan dipanggil adalah yang ditunjukkan oleh pointer n-th di vtable. "Mencari tahu" menyiratkan ada pemeriksaan jenis dan semacamnya, yang tidak terjadi. Once the run-time system figured out it would need to call a templatized virtual function
- Apakah fungsinya virtual atau tidak diketahui pada waktu kompilasi.
void f(concr_base& cb, virt_base& vb) { cb.f(); vb.f(); }
, maka ia "tahu" fungsi apa yang dipanggil pada titik yang cb.f()
dipanggil, dan tidak tahu itu untuk vb.f()
. Yang terakhir harus ditemukan saat runtime , oleh sistem runtime . Apakah Anda ingin menyebutnya "mencari tahu", dan apakah ini lebih atau kurang efisien, tidak sedikit mengubah fakta ini.
Dari C ++ Templates Panduan Lengkap:
Templat fungsi anggota tidak dapat dinyatakan virtual. Batasan ini dikenakan karena implementasi biasa dari mekanisme panggilan fungsi virtual menggunakan tabel ukuran tetap dengan satu entri per fungsi virtual. Namun, jumlah instantiations dari templat fungsi anggota tidak tetap sampai seluruh program telah diterjemahkan. Oleh karena itu, mendukung templat fungsi anggota virtual akan memerlukan dukungan untuk jenis mekanisme yang sama sekali baru dalam kompiler dan penghubung C ++. Sebaliknya, anggota biasa templat kelas bisa virtual karena jumlah mereka tetap ketika kelas dipakai
C ++ tidak mengizinkan fungsi anggota template virtual sekarang. Alasan yang paling mungkin adalah kompleksitas penerapannya. Rajendra memberikan alasan yang bagus mengapa hal itu tidak dapat dilakukan saat ini tetapi mungkin dengan perubahan standar yang masuk akal. Terutama mengetahui berapa banyak contoh fungsi templated sebenarnya ada dan membangun vtable tampaknya sulit jika Anda mempertimbangkan tempat panggilan fungsi virtual. Orang standar hanya memiliki banyak hal lain yang harus dilakukan sekarang dan C ++ 1x adalah banyak pekerjaan untuk penulis kompiler juga.
Kapan Anda membutuhkan fungsi anggota templated? Saya pernah menemukan situasi seperti itu di mana saya mencoba untuk memperbaiki hierarki dengan kelas dasar virtual murni. Itu adalah gaya yang buruk untuk menerapkan strategi yang berbeda. Saya ingin mengubah argumen dari salah satu fungsi virtual ke tipe numerik dan alih-alih membebani fungsi anggota dan menimpa setiap kelebihan di semua sub-kelas saya mencoba menggunakan fungsi templat virtual (dan harus mencari tahu bahwa itu tidak ada) .)
Mari kita mulai dengan beberapa latar belakang pada tabel fungsi virtual dan cara kerjanya ( sumber ):
[20.3] Apa perbedaan antara fungsi anggota virtual dan non-virtual?
Fungsi anggota non-virtual diselesaikan secara statis. Yaitu, fungsi anggota dipilih secara statis (pada waktu kompilasi) berdasarkan jenis penunjuk (atau referensi) ke objek.
Sebaliknya, fungsi anggota virtual diselesaikan secara dinamis (saat run-time). Artinya, fungsi anggota dipilih secara dinamis (saat run-time) berdasarkan pada jenis objek, bukan jenis pointer / referensi ke objek itu. Ini disebut "pengikatan dinamis." Kebanyakan kompiler menggunakan beberapa varian dari teknik berikut: jika objek memiliki satu atau lebih fungsi virtual, kompiler menempatkan pointer tersembunyi di objek yang disebut "virtual-pointer" atau "v-pointer." V-pointer ini menunjuk ke tabel global yang disebut "virtual-table" atau "v-table."
Compiler membuat tabel-v untuk setiap kelas yang memiliki setidaknya satu fungsi virtual. Sebagai contoh, jika class Circle memiliki fungsi virtual untuk draw () dan move () dan resize (), akan ada tepat satu v-table yang terkait dengan class Circle, bahkan jika ada trilyun objek Circle, dan v-pointer dari masing-masing objek Circle akan menunjuk ke Circle v-table. V-table itu sendiri memiliki pointer ke masing-masing fungsi virtual di kelas. Sebagai contoh, Circle v-table akan memiliki tiga pointer: pointer ke Circle :: draw (), pointer ke Circle :: move (), dan pointer ke Circle :: resize ().
Selama pengiriman fungsi virtual, sistem run-time mengikuti v-pointer objek ke tabel-v kelas, kemudian mengikuti slot yang sesuai dalam tabel-v ke kode metode.
Overhead ruang-biaya dari teknik di atas adalah nominal: pointer tambahan per objek (tetapi hanya untuk objek yang perlu melakukan pengikatan dinamis), ditambah pointer tambahan per metode (tetapi hanya untuk metode virtual). Overhead biaya-waktu juga cukup nominal: dibandingkan dengan panggilan fungsi normal, panggilan fungsi virtual membutuhkan dua pengambilan tambahan (satu untuk mendapatkan nilai v-pointer, yang kedua untuk mendapatkan alamat metode). Tak satu pun dari aktivitas runtime ini terjadi dengan fungsi non-virtual, karena kompilator menyelesaikan fungsi non-virtual secara eksklusif pada waktu kompilasi berdasarkan pada jenis pointer.
Saya mencoba menggunakan sesuatu seperti ini sekarang untuk kelas dasar cubefile dengan fungsi beban yang dioptimalkan templated yang akan diimplementasikan secara berbeda untuk berbagai jenis kubus (beberapa disimpan oleh pixel, beberapa oleh gambar, dll).
Beberapa kode:
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;
Apa yang saya inginkan, tetapi tidak dapat dikompilasi karena kombo templated virtual:
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;
Saya akhirnya memindahkan deklarasi templat ke tingkat kelas . Solusi ini akan memaksa program untuk mengetahui tentang tipe data tertentu yang akan mereka baca sebelum membacanya, yang tidak dapat diterima.
peringatan, ini tidak terlalu cantik tapi itu memungkinkan saya untuk menghapus kode eksekusi berulang
1) di kelas dasar
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) dan di kelas anak-anak
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);
Perhatikan bahwa LoadAnyCube tidak dideklarasikan di kelas dasar.
Berikut jawaban stack overflow lain dengan penyelesaian : perlu solusi anggota template virtual .
Kode berikut dapat dikompilasi dan dijalankan dengan benar, menggunakan MinGW G ++ 3.4.5 pada Window 7:
#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);
}
dan hasilnya adalah:
A:A<string> a
A<--B:B<string> c
A<--B:3
Dan kemudian saya menambahkan kelas X baru:
class X
{
public:
template <typename T>
virtual void func2(const T& p)
{
cout<<"C:"<<p<<endl;
}
};
Ketika saya mencoba menggunakan kelas X di main () seperti ini:
X x;
x.func2<string>("X x");
g ++ melaporkan kesalahan berikut:
vtempl.cpp:34: error: invalid use of `virtual' in template declaration of `virtu
al void X::func2(const T&)'
Jadi jelas bahwa:
Tidak, mereka tidak bisa. Tapi:
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);
};
memiliki banyak efek yang sama jika semua yang ingin Anda lakukan adalah memiliki antarmuka umum dan menunda implementasi ke subclass.
Foo
Pointer Anda memenuhi syarat karena Foo<Bar>
, tidak dapat menunjuk ke Foo<Barf>
atau Foo<XXX>
.
Tidak, fungsi anggota template tidak boleh virtual.
Di jawaban lain fungsi templat yang diusulkan adalah fasad dan tidak menawarkan manfaat praktis.
Bahasa tidak memungkinkan fungsi template virtual tetapi dengan solusi dimungkinkan untuk memiliki keduanya, misalnya satu implementasi template untuk setiap kelas dan antarmuka umum virtual.
Namun demikian perlu untuk mendefinisikan untuk setiap kombinasi jenis template fungsi pembungkus virtual dummy:
#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;
}
Keluaran:
Luas kotak adalah 1, Luas lingkaran adalah 3.1415926535897932385
Coba di sini
Untuk menjawab bagian kedua dari pertanyaan:
Jika mereka bisa virtual, apa contoh skenario di mana seseorang akan menggunakan fungsi seperti itu?
Ini bukan hal yang tidak masuk akal untuk dilakukan. Misalnya, Java (di mana setiap metode virtual) tidak memiliki masalah dengan metode generik.
Salah satu contoh dalam C ++ menginginkan templat fungsi virtual adalah fungsi anggota yang menerima iterator generik. Atau fungsi anggota yang menerima objek fungsi generik.
Solusi untuk masalah ini adalah dengan menggunakan tipe erasure dengan boost :: any_range dan boost :: function, yang akan memungkinkan Anda untuk menerima iterator atau functor generik tanpa perlu menjadikan fungsi Anda sebagai templat.
Ada solusi untuk 'metode templat virtual' jika rangkaian tipe untuk metode templat diketahui sebelumnya.
Untuk menunjukkan ide, dalam contoh di bawah ini hanya dua jenis yang digunakan ( int
dan double
).
Di sana, metode template 'virtual' ( Base::Method
) memanggil metode virtual yang sesuai (salah satunya Base::VMethod
) yang, pada gilirannya, memanggil implementasi metode template ( Impl::TMethod
).
Satu hanya perlu menerapkan metode template TMethod
dalam implementasi turunan ( AImpl
, BImpl
) dan digunakan 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;
}
Keluaran:
0
1
2
3
NB:
Base::Method
sebenarnya surplus untuk kode nyata ( VMethod
dapat dibuat publik dan digunakan secara langsung). Saya menambahkannya sehingga terlihat seperti metode template 'virtual' yang sebenarnya.
Base
kelas asli setiap kali Anda perlu memanggil fungsi template dengan tipe argumen yang tidak kompatibel dengan yang diterapkan sejauh ini. Menghindari keharusan ini adalah maksud dari templat ...
Sementara pertanyaan yang lebih tua yang telah dijawab oleh banyak orang, saya percaya metode ringkas, tidak begitu berbeda dari yang lain diposting, adalah menggunakan makro kecil untuk membantu memudahkan duplikasi deklarasi kelas.
// 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;
// ...
};
Jadi sekarang, untuk mengimplementasikan subclass kami:
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
};
Manfaatnya di sini adalah, ketika menambahkan jenis yang baru didukung, semuanya dapat dilakukan dari header abstrak dan mengabaikan kemungkinan memperbaikinya dalam beberapa file sumber / header.
Setidaknya dengan gcc 5.4 fungsi virtual bisa menjadi anggota templat tetapi harus templat sendiri.
#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;
}
Keluaran
mix before a2
Process finished with exit code 0
Coba ini:
Tulis di 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);
}
Periksa, jika bekerja dengan ini, untuk menulis kode ini di main.cpp:
#include <iostream>
#include <classeder.h>
int main()
{
Example exmpl;
exmpl.c_value = "Hello, world!";
std::cout << exmpl.VirtualFunc(exmpl);
return 0;
}