it-swarm.asia

Bisakah templat fungsi anggota kelas C++ menjadi virtual?

Saya telah mendengar bahwa templat fungsi anggota kelas C++ tidak bisa virtual. Apakah ini benar? 

Jika mereka bisa virtual, apa contoh skenario di mana orang akan menggunakan fungsi seperti itu

261
WannaBeGeek

Templat adalah semua tentang kode penghasil kompilator di waktu kompilasi. Fungsi virtual adalah semua tentang sistem run-time yang mencari tahu fungsi mana yang dipanggil pada 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 type erasure 

289
sbi

Dari C++ Templates Panduan Lengkap:

Templat fungsi anggota tidak dapat dinyatakan virtual. Batasan ini dikenakan karena implementasi biasa dari fungsi virtual mekanisme panggilan menggunakan tabel ukuran tetap dengan satu entri per virtual fungsi. Namun, jumlah instansi dari fungsi anggota template tidak diperbaiki sampai seluruh program telah diterjemahkan . Oleh karena itu, mendukung templat fungsi anggota virtual akan membutuhkan mendukung jenis mekanisme baru dalam kompiler C++ dan penghubung. Sebaliknya, anggota biasa templat kelas bisa virtual karena jumlah mereka diperbaiki ketika kelas dipakai

104
InQusitive

C++ tidak mengizinkan fungsi anggota templat 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 wajar. Terutama mengetahui berapa banyak contoh fungsi templated yang benar-benar 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 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) .) 

31
pmr

Tabel Fungsi Virtual

Mari kita mulai dengan beberapa latar belakang pada tabel fungsi virtual dan cara kerjanya ( source ):

[20.3] Apa perbedaan antara virtual dan non-virtual fungsi anggota disebut?

Fungsi anggota non-virtual diselesaikan secara statis. Itu adalah fungsi anggota dipilih secara statis (pada waktu kompilasi) berdasarkan jenis pointer (atau referensi) ke objek.

Sebaliknya, fungsi anggota virtual diselesaikan secara dinamis (pada Run-time). Yaitu, fungsi anggota dipilih secara dinamis (pada 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: if objek memiliki satu atau lebih fungsi virtual, kompiler menempatkan .__ tersembunyi. pointer di objek yang disebut "virtual-pointer" atau "v-pointer." Ini v-pointer menunjuk ke tabel global yang disebut "tabel virtual" atau "v-table."

Kompiler membuat tabel-v untuk setiap kelas yang memiliki setidaknya satu fungsi virtual. Misalnya, jika kelas Circle memiliki fungsi virtual untuk draw () dan move () dan resize (), akan ada tepat satu v-table terkait dengan Lingkaran kelas, bahkan jika ada Lingkaran trilyun objek, dan v-pointer dari masing-masing objek Circle akan menunjuk ke lingkaran v-table. Tabel-v itu sendiri memiliki pointer ke masing-masing fungsi virtual di kelas. Sebagai contoh, Circle v-table akan memiliki tiga petunjuk: pointer ke Circle :: draw (), pointer ke Circle :: move (), dan sebuah pointer ke Circle :: resize ().

Selama pengiriman fungsi virtual, sistem run-time mengikuti objek v-pointer ke tabel-v kelas, kemudian ikuti slot yang sesuai dalam tabel-v ke kode metode.

Biaya overhead ruang dari teknik di atas adalah nominal: tambahan pointer per objek (tetapi hanya untuk objek yang perlu melakukan dynamic binding), ditambah pointer tambahan per metode (tetapi hanya untuk virtual metode). Biaya overhead waktu juga cukup nominal: dibandingkan dengan panggilan fungsi normal, panggilan fungsi virtual membutuhkan dua tambahan fetches (satu untuk mendapatkan nilai dari v-pointer, satu detik untuk mendapatkan alamat .__ dari metode ini). Tidak ada aktivitas runtime ini yang terjadi dengan fungsi non-virtual, karena kompiler menyelesaikan non-virtual fungsi secara eksklusif pada waktu kompilasi berdasarkan pada jenis penunjuk.


Masalah saya, atau bagaimana saya datang ke sini

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 level kelas. Solusi ini akan memaksa program untuk mengetahui tentang tipe data tertentu yang akan mereka baca sebelum membacanya, yang tidak dapat diterima.

Larutan

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 lainnya dengan sebuah solusi: perlu solusi anggota template virtual

16
Mark Essel

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.

11
Tom

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:

  • fungsi anggota virtual dapat digunakan dalam templat kelas. Mudah bagi kompiler untuk membuat vtable
  • Tidak mungkin mendefinisikan fungsi anggota templat kelas sebagai virtual, seperti yang Anda lihat, sulit untuk menentukan fungsi tanda tangan dan mengalokasikan entri vtable.
11
Brent81

Tidak, fungsi anggota template tidak boleh virtual. 

4
dirkgently

Untuk menjawab bagian kedua dari pertanyaan:

Jika mereka bisa virtual, apa contoh skenario di mana orang akan menggunakan fungsi seperti itu

Ini bukan hal yang tidak masuk akal untuk dilakukan. Sebagai contoh, 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 menggunakan tipe erasure dengan boost :: any_range dan boost :: function, yang akan memungkinkan Anda untuk menerima iterator atau functor generik tanpa harus menjadikan fungsi Anda sebagai templat.

3
exclipy

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 satu dari Base::VMethod) yang, pada gilirannya, memanggil implementasi metode template (Impl::TMethod).

Satu hanya perlu mengimplementasikan metode template TMethod dalam implementasi turunan (AImpl, BImpl) dan menggunakan 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 asli (VMethod dapat dibuat publik dan digunakan secara langsung) . Saya menambahkannya sehingga terlihat seperti metode templat 'virtual' yang sebenarnya.

2
sad1raf

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
0
Maxim Sinev

Di jawaban lain fungsi templat yang diusulkan adalah fasad dan tidak menawarkan manfaat praktis.

  • Fungsi template berguna untuk menulis kode hanya sekali menggunakan Berbagai jenis. 
  • Fungsi virtual berguna untuk memiliki antarmuka umum untuk kelas yang berbeda.

Bahasa tidak mengizinkan fungsi templat virtual tetapi dengan penyelesaiannya dimungkinkan untuk memiliki keduanya, mis. satu implementasi template untuk setiap kelas dan antarmuka umum virtual.

Namun perlu untuk menentukan 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

Cobalah di sini

0
andreaplanet