Czy szablon funkcji członka klasy może być wirtualny?

304

Słyszałem, że szablony funkcji członka klasy C ++ nie mogą być wirtualne. Czy to prawda?

Jeśli mogą być wirtualne, jaki jest przykład scenariusza, w którym można by użyć takiej funkcji?

WannaBeGeek
źródło
12
Miałem do czynienia z podobnym problemem, a także dowiedziałem się, że kontrowersja jest jednocześnie bycie wirtualnym i szablonem. Moim rozwiązaniem było napisanie magii szablonu, która będzie powszechna wśród klas pochodnych i wywołanie czystej funkcji wirtualnej, która wykonuje wyspecjalizowaną część. Jest to oczywiście związane z charakterem mojego problemu, więc może nie działać w każdym przypadku.
Tamás Szelei

Odpowiedzi:

329

Szablony dotyczą generowania kodu kompilatora w czasie kompilacji . Funkcje wirtualne polegają na tym, aby system wykonawczy zastanawiał się, którą funkcję wywołać w czasie wykonywania .

Gdy system wykonawczy zorientuje się, że będzie musiał wywołać funkcję wirtualną opartą na szablonie, kompilacja jest zakończona, a kompilator nie może już wygenerować odpowiedniej instancji. Dlatego nie możesz mieć szablonów funkcji wirtualnego członka.

Istnieje jednak kilka potężnych i interesujących technik wynikających z łączenia polimorfizmu i szablonów, w szczególności tak zwane wymazywanie typu .

sbi
źródło
32
Nie widzę powodu, dla którego jest to język , tylko powody implementacji . vtables nie są częścią języka - tylko standardowy sposób kompilacji implementujący język.
gerardw
16
Virtual functions are all about the run-time system figuring out which function to call at run-time- przepraszam, ale jest to raczej zły sposób i dość mylący. Jest to tylko pośrednia i nie ma w tym przypadku żadnego „środowiska uruchomieniowego”, w czasie kompilacji wiadomo, że wywoływana funkcja jest wskazywana przez n-ty wskaźnik w tabeli. „Zrozumienie” oznacza, że ​​istnieją kontrole typu i tak nie jest. Once the run-time system figured out it would need to call a templatized virtual function- w czasie kompilacji wiadomo, czy funkcja jest wirtualna.
dtech,
9
@ddriver: 1. Jeśli kompilatory zobaczą void f(concr_base& cb, virt_base& vb) { cb.f(); vb.f(); }, to „wie”, która funkcja jest wywoływana w punkcie cb.f()wywoływanym, i nie wie o tym vb.f(). Ta ostatnia musi znaleźć się w czasie pracy , w systemie czasu . To, czy chcesz nazwać to „rozgryzaniem” i czy jest to mniej lub bardziej wydajne, nie zmienia tych faktów.
sbi
9
@ddriver: 2. Instancje szablonów funkcji (członka) są funkcjami (członka), więc nie ma problemu z umieszczeniem wskaźnika takiej instancji w vtable. Jednak, które instancje szablonu są potrzebne, są znane tylko podczas kompilacji programu wywołującego, natomiast tabele vtable są konfigurowane podczas kompilacji klasy bazowej i klas pochodnych. I wszystkie są kompilowane osobno. Co gorsza - nowe klasy pochodne można łączyć z działającymi systemami w czasie wykonywania (pomyśl, że Twoja przeglądarka dynamicznie ładuje wtyczkę). Nawet kod źródłowy osoby wywołującej może zostać utracony podczas tworzenia nowej klasy pochodnej.
sbi
9
@sbi: Dlaczego przyjmujesz założenia oparte na moim nazwisku? Nie pomyliłem generycznych i szablonów. Wiem, że ogólne elementy języka Java to czysto czas działania. Nie wyjaśniłeś wyczerpująco, dlaczego nie możesz mieć szablonów funkcji wirtualnych elementów w C ++, ale zrobił to InQsitive. Zbytnio uprościłeś szablon i mechanikę wirtualną, aby „skompilować czas” w porównaniu z „czasem wykonania” i doszedł do wniosku, że „nie możesz mieć szablonów funkcji wirtualnych elementów”. Odniosłem się do odpowiedzi InQsitive, która odwołuje się do „Szablony C ++ Kompletny przewodnik”. Nie uważam tego za „machanie ręką”. Miłego dnia.
Javanator,
133

Z szablonów C ++ Kompletny przewodnik:

Szablony funkcji składowych nie mogą być deklarowane jako wirtualne. To ograniczenie jest narzucone, ponieważ zwykła implementacja mechanizmu wywoływania funkcji wirtualnej używa tabeli o stałym rozmiarze z jednym wpisem na funkcję wirtualną. Jednak liczba wystąpień szablonu funkcji składowej nie jest ustalona, ​​dopóki cały program nie zostanie przetłumaczony. Dlatego też obsługa szablonów funkcji wirtualnych elementów składowych wymagałaby obsługi zupełnie nowego rodzaju mechanizmu w kompilatorach i linkerach C ++. Natomiast zwykli członkowie szablonów klas mogą być wirtualni, ponieważ ich liczba jest ustalana podczas tworzenia instancji klasy

Niewątpliwy
źródło
8
Myślę, że dzisiejszy kompilator i konsolidatory C ++, zwłaszcza z obsługą optymalizacji czasu łącza, powinny być w stanie wygenerować wymagane tabele i przesunięcia w czasie łącza. Więc może otrzymamy tę funkcję w C ++ 2b?
Kai Petzke,
33

C ++ nie zezwala obecnie na funkcje wirtualnych elementów szablonu. Najbardziej prawdopodobną przyczyną jest złożoność jego wdrożenia. Rajendra podaje dobry powód, dla którego nie można tego zrobić teraz, ale byłoby to możliwe przy rozsądnych zmianach standardu. Zwłaszcza ustalenie, ile instancji funkcji szablonowej faktycznie istnieje, a zbudowanie tabeli vt wydaje się trudne, jeśli weźmie się pod uwagę miejsce wirtualnego wywołania funkcji. Standardy ludzie mają teraz wiele innych rzeczy do zrobienia, a C ++ 1x to także dużo pracy dla autorów kompilatorów.

Kiedy potrzebujesz szablonowej funkcji członka? Kiedyś natknąłem się na taką sytuację, w której próbowałem refaktoryzować hierarchię za pomocą czystej wirtualnej klasy bazowej. Był to zły styl wdrażania różnych strategii. Chciałem zmienić argument jednej z funkcji wirtualnych na typ liczbowy i zamiast przeciążać funkcję członka i zastępować każde przeciążenie we wszystkich podklasach, próbowałem użyć funkcji wirtualnych szablonów (i musiałem się dowiedzieć, że one nie istnieją .)

pmr
źródło
5
@pmr: Wirtualna funkcja może zostać wywołana z kodu, który nawet nie istniał podczas kompilacji funkcji. W jaki sposób kompilator określi, które wystąpienia (teoretycznej) funkcji wirtualnego elementu szablonu mają zostać wygenerowane dla kodu, który nawet nie istnieje?
sbi
2
@sbi: Tak, osobna kompilacja byłaby ogromnym problemem. W ogóle nie jestem ekspertem od kompilatorów C ++, więc nie mogę zaoferować rozwiązania. Podobnie jak w przypadku funkcji szablonowych, należy ją ponownie utworzyć w każdej jednostce kompilacyjnej, prawda? Czy to nie rozwiązałoby problemu?
pmr
2
@sbi, jeśli masz na myśli dynamiczne ładowanie bibliotek, to ogólny problem z klasami / funkcjami szablonów, a nie tylko z wirtualnymi metodami szablonów.
Oak
„C ++ nie pozwala [...]” - doceniłbym odniesienie do standardu (bez względu na to, czy ten był aktualny, kiedy odpowiedź została napisana, czy ten aktualny osiem lat później) ...
Aconcagua
19

Tabele funkcji wirtualnych

Zacznijmy od pewnego tła na temat tabel funkcji wirtualnych i ich działania ( źródło ):

[20.3] Jaka jest różnica między wywoływaniem wirtualnych i niewirtualnych funkcji składowych?

Nie-wirtualne funkcje składowe są rozwiązywane statycznie. Oznacza to, że funkcja członka jest wybierana statycznie (w czasie kompilacji) na podstawie typu wskaźnika (lub odwołania) do obiektu.

Natomiast funkcje wirtualnych elementów są rozwiązywane dynamicznie (w czasie wykonywania). Oznacza to, że funkcja członka jest wybierana dynamicznie (w czasie wykonywania) na podstawie typu obiektu, a nie typu wskaźnika / odwołania do tego obiektu. Nazywa się to „wiązaniem dynamicznym”. Większość kompilatorów stosuje pewien wariant następującej techniki: jeśli obiekt ma jedną lub więcej funkcji wirtualnych, kompilator umieszcza w obiekcie ukryty wskaźnik zwany „wskaźnikiem wirtualnym” lub „wskaźnikiem v”. Ten wskaźnik v wskazuje na globalną tabelę zwaną „wirtualną tabelą” lub „tabelą v”.

Kompilator tworzy tabelę v dla każdej klasy, która ma co najmniej jedną funkcję wirtualną. Na przykład, jeśli klasa Circle ma funkcje wirtualne dla draw () oraz move () i resize (), to z klasą Circle byłby dokładnie jeden stół v, nawet gdyby istniały obiekty gazillionowe Circle, a wskaźnik v każdy z tych obiektów Circle wskazywałby na v-stół Circle. Sama tabela v ma wskaźniki do każdej funkcji wirtualnej w klasie. Na przykład tabela v Circle miałaby trzy wskaźniki: wskaźnik do Circle :: draw (), wskaźnik do Circle :: move () i wskaźnik do Circle :: resize ().

Podczas wysyłania funkcji wirtualnej system wykonawczy podąża za wskaźnikiem v obiektu do tabeli v klasy, a następnie pod odpowiednim gniazdem w tabeli v do kodu metody.

Koszty ogólne związane z powyższą techniką są nominalne: dodatkowy wskaźnik na obiekt (ale tylko w przypadku obiektów, które będą musiały wykonać dynamiczne wiązanie) oraz dodatkowy wskaźnik na metodę (ale tylko w przypadku metod wirtualnych). Narzut kosztów czasu jest również dość nominalny: w porównaniu do normalnego wywołania funkcji, wirtualne wywołanie funkcji wymaga dwóch dodatkowych pobrań (jeden, aby uzyskać wartość wskaźnika v, drugi, aby uzyskać adres metody). Żadne z tych działań środowiska wykonawczego nie występuje w przypadku funkcji niebędących wirtualnymi, ponieważ kompilator rozwiązuje funkcje niebędące wirtualnymi wyłącznie w czasie kompilacji na podstawie typu wskaźnika.


Mój problem lub jak tu przybyłem

Próbuję teraz użyć czegoś takiego dla klasy bazowej pliku kostek z zoptymalizowanymi funkcjami ładowania zoptymalizowanymi w szablonie, które będą wdrażane inaczej dla różnych typów kostek (niektóre przechowywane w pikselach, inne w obrazach itp.).

Jakiś kod:

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;

To, co chciałbym, żeby było, ale nie będzie się kompilowało z powodu wirtualnego zestawu szablonów:

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;

Skończyłem przenoszenie deklaracji szablonu na poziom klasy . To rozwiązanie zmusiłoby programy do znajomości określonych rodzajów danych, które czytałyby przed ich odczytaniem, co jest niedopuszczalne.

Rozwiązanie

Uwaga, to nie jest bardzo ładne, ale pozwoliło mi to usunąć powtarzający się kod wykonawczy

1) w klasie bazowej

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) oraz w klasach dziecięcych

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

Zauważ, że LoadAnyCube nie jest zadeklarowany w klasie podstawowej.


Oto kolejna odpowiedź na przepełnienie stosu z obejściem: potrzebujesz obejścia elementu wirtualnego szablonu .

Mark Essel
źródło
1
Spotkałem tę samą sytuację i strukturę dziedziczenia klas masowych. makra pomogły.
ZFY
16

Poniższy kod można skompilować i działa poprawnie, używając MinGW G ++ 3.4.5 w Windows 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);
}

a wynikiem jest:

A:A<string> a
A<--B:B<string> c
A<--B:3

A później dodałem nową klasę X:

class X
{
public:
    template <typename T>
    virtual void func2(const T& p)
    {
        cout<<"C:"<<p<<endl;
    }
};

Kiedy próbowałem użyć klasy X w main () w następujący sposób:

X x;
x.func2<string>("X x");

g ++ zgłoś następujący błąd:

vtempl.cpp:34: error: invalid use of `virtual' in template declaration of `virtu
al void X::func2(const T&)'

Jest więc oczywiste, że:

  • wirtualna funkcja członka może być używana w szablonie klasy. Kompilator łatwo konstruuje vtable
  • Niemożliwe jest zdefiniowanie funkcji elementu szablonu klasy jako wirtualnej, jak widać, trudno jest określić podpis funkcji i przydzielić wpisy vtable.
Brent81
źródło
19
Szablon klasy może mieć wirtualne funkcje składowe. Funkcja członka może nie być zarówno szablonem funkcji członka, jak i wirtualną funkcją członka.
James McNellis
1
tak naprawdę nie działa z gcc 4.4.3. W moim systemie na pewno Ubuntu 10.04
blueskin
3
Jest to zupełnie inne niż pytanie. Tutaj cała klasa bazowa jest wzorowana. Kompilowałem już takie rzeczy. Skompiluje się to również w Visual Studio 2010
ds-bos-msk
14

Nie, nie mogą. Ale:

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

ma taki sam efekt, jeśli wszystko, co chcesz zrobić, to mieć wspólny interfejs i odroczyć implementację do podklas.

Tomek
źródło
3
Jest to znane jako CRTP, jeśli ktoś jest ciekawy.
Michael Choi,
1
Ale to nie pomaga w przypadkach, w których ktoś ma hierarchię klas i chce móc wywoływać wirtualne metody wskaźników do klas podstawowych. Twój Foowskaźnik jest kwalifikowany jako Foo<Bar>, nie może wskazywać na Foo<Barf>lub Foo<XXX>.
Kai Petzke,
@KaiPetzke: Nie możesz zbudować nieograniczonego wskaźnika, nie. Ale możesz utworzyć szablon dowolnego kodu, który nie musi znać konkretnego typu, który ma taki sam efekt (przynajmniej koncepcyjnie - oczywiście zupełnie inna implementacja).
Tom
8

Nie, funkcje elementów szablonu nie mogą być wirtualne.

bezpośrednio
źródło
9
Moja ciekawość to: dlaczego? Jakie problemy napotyka kompilator?
WannaBeGeek
1
Potrzebujesz deklaracji w zakresie (przynajmniej, aby uzyskać prawidłowe typy). Norma (i język) wymaga posiadania deklaracji w zakresie identyfikatorów, których używasz.
bezpośrednio
4

W pozostałych odpowiedziach proponowana funkcja szablonu jest fasadą i nie oferuje żadnych praktycznych korzyści.

  • Funkcje szablonów są przydatne do pisania kodu tylko raz przy użyciu różnych typów.
  • Funkcje wirtualne są przydatne do posiadania wspólnego interfejsu dla różnych klas.

Język nie pozwala na korzystanie z funkcji wirtualnych szablonów, ale dzięki obejściu można mieć oba, np. Jedną implementację szablonu dla każdej klasy i wspólny wirtualny interfejs.

Konieczne jest jednak zdefiniowanie dla każdej kombinacji typów szablonów funkcji wirtualnego opakowania wirtualnego:

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

Wynik:

Pole kwadratu to 1, pole okręgu to 3.1415926535897932385

Wypróbuj tutaj

andreaplanet
źródło
3

Aby odpowiedzieć na drugą część pytania:

Jeśli mogą być wirtualne, jaki jest przykład scenariusza, w którym można by użyć takiej funkcji?

To nie jest nierozsądna rzecz, którą chcesz zrobić. Na przykład Java (gdzie każda metoda jest wirtualna) nie ma problemów z metodami ogólnymi.

Jednym z przykładów w C ++ potrzeby szablonu funkcji wirtualnej jest funkcja członka, która akceptuje ogólny iterator. Lub funkcja składowa, która akceptuje ogólny obiekt funkcji.

Rozwiązaniem tego problemu jest użycie funkcji wymazywania z funkcją boost :: any_range i boost :: function, która pozwoli ci zaakceptować ogólny iterator lub funktor bez potrzeby zmiany funkcji w szablon.

exclipy
źródło
6
Generics Java to cukier syntaktyczny do odlewania. Nie są takie same jak szablony.
Brice M. Dempsey
2
@ BriceM.Dempsey: Można powiedzieć, że casting to sposób, w jaki Java implementuje generyczne, a nie na odwrót ... i osobiście, eklipy przypadków użycia są prawidłowe IMO.
einpoklum
2

Istnieje sposób obejścia „wirtualnej metody szablonu”, jeśli zestaw typów dla metody szablonu jest znany z góry.

Aby pokazać pomysł, w poniższym przykładzie użyto tylko dwóch typów ( inti double).

Tam metoda „wirtualnego” szablonu ( Base::Method) wywołuje odpowiednią metodę wirtualną (jedną z nich Base::VMethod), która z kolei wywołuje implementację metody szablonu ( Impl::TMethod).

Wystarczy zaimplementować metodę szablonu TMethod w implementacjach pochodnych ( AImpl, BImpl) i użyć 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;
}

Wynik:

0
1
2
3

NB: Base::Methodjest faktycznie nadwyżką dla prawdziwego kodu (VMethod może być upubliczniona i używana bezpośrednio). Dodałem go, aby wyglądał jak rzeczywista metoda „wirtualnego” szablonu.

sad1raf
źródło
Wymyśliłem to rozwiązanie, rozwiązując problem w pracy. Wydaje się podobny do powyższego Mark Essel, ale mam nadzieję, że lepiej go wdrożyć i wyjaśnić.
sad1raf
Już zakwalifikowałbym to jako zaciemnienie kodu, a ty wciąż nie rozumiesz, że musisz modyfikować oryginalną Baseklasę za każdym razem, gdy musisz wywoływać funkcję szablonu z typem argumentu niezgodnym z tymi, które zostały zaimplementowane do tej pory. Unikanie tej konieczności jest intencją szablonów ...
Aconcagua
Podejście Esselsa jest zupełnie inne: zwykłe funkcje wirtualne akceptujące różne instancje szablonów - a końcowa funkcja szablonów w klasie pochodnej służy jedynie do unikania powielania kodu i nie ma nawet części przeciwnej w klasie bazowej ...
Aconcagua
2

Podczas gdy starsze pytanie, na które wiele osób odpowiedziało, uważam, że zwięzłą metodą, nie różniącą się tak bardzo od innych opublikowanych, jest użycie drobnego makra, aby ułatwić powielanie deklaracji klasowych.

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

Aby teraz wdrożyć naszą podklasę:

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

Zaletą tego jest to, że dodając nowy obsługiwany typ, można to wszystko zrobić z abstrakcyjnego nagłówka i zrezygnować z możliwości jego naprawy w wielu plikach źródłowych / nagłówkowych.

mccatnm
źródło
0

Przynajmniej funkcje wirtualne gcc 5.4 mogą być elementami szablonu, ale same szablony muszą być.

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

Wyjścia

mix before a2
Process finished with exit code 0
Maxim Sinev
źródło
0

Spróbuj tego:

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

Zaznacz, jeśli pracujesz z tym, aby napisać ten kod w main.cpp:

#include <iostream>
#include <classeder.h>

int main()
{
     Example exmpl;
     exmpl.c_value = "Hello, world!";
     std::cout << exmpl.VirtualFunc(exmpl);
     return 0;
}
GobeRadJem32
źródło