Kann eine Funktionsvorlage für Klassenmitglieder virtuell sein?

304

Ich habe gehört, dass Vorlagen für C ++ - Klassenmitgliedsfunktionen nicht virtuell sein können. Ist das wahr?

Wenn sie virtuell sein können, was ist ein Beispiel für ein Szenario, in dem man eine solche Funktion verwenden würde?

WannaBeGeek
quelle
12
Ich hatte ein ähnliches Problem und erfuhr auch, dass es umstritten ist, gleichzeitig virtuell und vorlagenweise zu sein. Meine Lösung bestand darin, die Vorlagenmagie zu schreiben, die in den abgeleiteten Klassen üblich ist, und eine reine virtuelle Funktion aufzurufen, die den speziellen Teil ausführt. Dies hängt natürlich mit der Art meines Problems zusammen und funktioniert möglicherweise nicht in jedem Fall.
Tamás Szelei

Antworten:

329

In Vorlagen dreht sich alles um den Compiler, der zur Kompilierungszeit Code generiert . Bei virtuellen Funktionen dreht sich alles um das Laufzeitsystem, um herauszufinden, welche Funktion zur Laufzeit aufgerufen werden soll .

Sobald das Laufzeitsystem herausgefunden hat, dass es eine virtuelle Funktion mit Vorlagen aufrufen muss, ist die Kompilierung abgeschlossen und der Compiler kann die entsprechende Instanz nicht mehr generieren. Daher können Sie keine Funktionsvorlagen für virtuelle Elemente haben.

Es gibt jedoch einige leistungsstarke und interessante Techniken, die sich aus der Kombination von Polymorphismus und Vorlagen ergeben, insbesondere die sogenannte Typlöschung .

sbi
quelle
32
Ich sehe keinen Sprachgrund dafür, nur Implementierungsgründe . vtables sind nicht Teil der Sprache - nur die Standardmethode, mit der Compiler die Sprache implementieren.
Gerardw
16
Virtual functions are all about the run-time system figuring out which function to call at run-time- Entschuldigung, aber das ist ein ziemlich falscher Weg und ziemlich verwirrend. Es ist nur eine Indirektion, und es ist keine "Laufzeitermittlung" beteiligt. Während der Kompilierungszeit ist bekannt, dass die aufzurufende Funktion diejenige ist, auf die der n-te Zeiger in der vtable zeigt. "Herausfinden" impliziert, dass es Typprüfungen und dergleichen gibt, was nicht der Fall ist. Once the run-time system figured out it would need to call a templatized virtual function- Ob die Funktion virtuell ist oder nicht, ist zur Kompilierungszeit bekannt.
dtech
9
@ddriver: 1. Wenn der Compiler sieht void f(concr_base& cb, virt_base& vb) { cb.f(); vb.f(); }, "weiß" er, welche Funktion an dem cb.f()aufgerufenen Punkt aufgerufen wird, und weiß das nicht für vb.f(). Letztere muss herausgefunden, zur Laufzeit , durch das Laufzeitsystem . Ob Sie dies als "Herausfinden" bezeichnen möchten und ob dies mehr oder weniger effizient ist, ändert nichts an diesen Fakten.
sbi
9
@ddriver: 2. Instanzen von (Mitglieds-) Funktionsvorlagen sind (Mitglieds-) Funktionen, daher ist es überhaupt kein Problem, einen Zeiger auf eine solche Instanz in die vtable zu setzen. Welche Vorlageninstanzen benötigt werden, ist jedoch nur bekannt, wenn der Aufrufer kompiliert wird, während die vtables eingerichtet werden, wenn die Basisklasse und die abgeleiteten Klassen kompiliert werden. Und diese werden alle separat zusammengestellt. Schlimmer noch - neue abgeleitete Klassen können zur Laufzeit mit laufenden Systemen verknüpft werden (denken Sie, Ihr Browser lädt ein Plugin dynamisch). Sogar der Quellcode des Aufrufers kann lange verloren gehen, wenn eine neue abgeleitete Klasse erstellt wird.
sbi
9
@sbi: Warum machst du Annahmen basierend auf meinem Namen? Ich habe Generika und Vorlagen nicht verwechselt. Ich weiß, dass Javas Generika reine Laufzeit sind. Sie haben nicht ausführlich erklärt, warum Sie in C ++ keine Funktionsvorlagen für virtuelle Elemente haben können, InQsitive jedoch. Sie haben die Vorlage und die virtuelle Mechanik zu stark vereinfacht, um die Kompilierungszeit mit der Laufzeit zu vergleichen, und sind zu dem Schluss gekommen, dass "Sie keine Funktionsvorlagen für virtuelle Elemente haben können". Ich habe auf die Antwort von InQsitive verwiesen, die auf "C ++ Templates The Complete Guide" verweist. Ich halte das nicht für "Handwinken". Einen schönen Tag noch.
Javanator
133

Aus C ++ - Vorlagen Das vollständige Handbuch:

Mitgliedsfunktionsvorlagen können nicht als virtuell deklariert werden. Diese Einschränkung wird auferlegt, da bei der üblichen Implementierung des Aufrufmechanismus für virtuelle Funktionen eine Tabelle mit fester Größe mit einem Eintrag pro virtueller Funktion verwendet wird. Die Anzahl der Instanziierungen einer Elementfunktionsvorlage wird jedoch erst festgelegt, wenn das gesamte Programm übersetzt wurde. Die Unterstützung von Funktionsvorlagen für virtuelle Elemente würde daher die Unterstützung einer völlig neuen Art von Mechanismus in C ++ - Compilern und Linkern erfordern. Im Gegensatz dazu können die normalen Mitglieder von Klassenvorlagen virtuell sein, da ihre Anzahl festgelegt ist, wenn eine Klasse instanziiert wird

InQusitive
quelle
8
Ich denke, der heutige C ++ - Compiler und Linker, insbesondere mit Unterstützung für die Optimierung der Verbindungszeit, sollten in der Lage sein, die erforderlichen vtables und Offsets zur Verbindungszeit zu generieren. Vielleicht bekommen wir diese Funktion in C ++ 2b?
Kai Petzke
33

C ++ erlaubt derzeit keine Funktionen für virtuelle Vorlagenmitglieder. Der wahrscheinlichste Grund ist die Komplexität der Implementierung. Rajendra gibt einen guten Grund an, warum dies derzeit nicht möglich ist, aber es könnte mit vernünftigen Änderungen des Standards möglich sein. Insbesondere wenn Sie den Ort des virtuellen Funktionsaufrufs berücksichtigen, ist es schwierig, herauszufinden, wie viele Instanziierungen einer Vorlagenfunktion tatsächlich vorhanden sind, und die vtable aufzubauen. Standard-Leute haben gerade eine Menge anderer Dinge zu tun und C ++ 1x ist auch für die Compiler-Autoren eine Menge Arbeit.

Wann würden Sie eine Vorlagenelementfunktion benötigen? Ich bin einmal auf eine solche Situation gestoßen, in der ich versucht habe, eine Hierarchie mit einer reinen virtuellen Basisklasse umzugestalten. Es war ein schlechter Stil für die Umsetzung verschiedener Strategien. Ich wollte das Argument einer der virtuellen Funktionen in einen numerischen Typ ändern und anstatt die Elementfunktion zu überladen und jede Überladung in allen Unterklassen zu überschreiben, habe ich versucht, virtuelle Vorlagenfunktionen zu verwenden (und musste herausfinden, dass sie nicht existieren .)

pmr
quelle
5
@pmr: Eine virtuelle Funktion wird möglicherweise aus Code aufgerufen, der beim Kompilieren der Funktion noch nicht vorhanden war. Wie würde der Compiler bestimmen, welche Instanzen eines (theoretischen) virtuellen Vorlagenmitglieds für Code generiert werden sollen, der noch nicht einmal existiert?
sbi
2
@sbi: Ja, eine separate Kompilierung wäre ein großes Problem. Ich bin überhaupt kein Experte für C ++ - Compiler, daher kann ich keine Lösung anbieten. Wie bei Vorlagenfunktionen im Allgemeinen sollte es in jeder Kompilierungseinheit erneut instanziiert werden, oder? Würde das das Problem nicht lösen?
PMR
2
@sbi Wenn Sie sich auf das dynamische Laden von Bibliotheken beziehen, ist dies ein allgemeines Problem bei Vorlagenklassen / -funktionen, nicht nur bei virtuellen Vorlagenmethoden.
Eiche
"C ++ erlaubt nicht [...]" - würde gerne einen Verweis auf den Standard sehen (egal ob derjenige, der zum Zeitpunkt der Erstellung der Antwort auf dem neuesten Stand war, oder derjenige, der acht Jahre später auf dem neuesten Stand war) ...
Aconcagua
19

Virtuelle Funktionstabellen

Beginnen wir mit einigen Hintergrundinformationen zu virtuellen Funktionstabellen und ihrer Funktionsweise ( Quelle ):

[20.3] Was ist der Unterschied zwischen dem Aufruf virtueller und nicht virtueller Mitgliedsfunktionen?

Nicht virtuelle Mitgliedsfunktionen werden statisch aufgelöst. Das heißt, die Elementfunktion wird statisch (zur Kompilierungszeit) basierend auf dem Typ des Zeigers (oder der Referenz) auf das Objekt ausgewählt.

Im Gegensatz dazu werden virtuelle Elementfunktionen dynamisch (zur Laufzeit) aufgelöst. Das heißt, die Elementfunktion wird dynamisch (zur Laufzeit) basierend auf dem Typ des Objekts ausgewählt, nicht auf dem Typ des Zeigers / Verweises auf dieses Objekt. Dies wird als "dynamische Bindung" bezeichnet. Die meisten Compiler verwenden eine Variante der folgenden Technik: Wenn das Objekt eine oder mehrere virtuelle Funktionen hat, fügt der Compiler einen versteckten Zeiger in das Objekt ein, der als "virtueller Zeiger" oder "v-Zeiger" bezeichnet wird. Dieser V-Zeiger zeigt auf eine globale Tabelle, die als "virtuelle Tabelle" oder "V-Tabelle" bezeichnet wird.

Der Compiler erstellt eine V-Tabelle für jede Klasse mit mindestens einer virtuellen Funktion. Wenn die Klasse Circle beispielsweise virtuelle Funktionen für draw () und move () und resize () hat, ist der Klasse Circle genau eine V-Tabelle zugeordnet, selbst wenn es eine Unmenge von Circle-Objekten und den V-Zeiger von gibt Jedes dieser Circle-Objekte würde auf die Circle-V-Tabelle verweisen. Die V-Tabelle selbst enthält Zeiger auf jede der virtuellen Funktionen in der Klasse. Beispielsweise hätte die Circle-V-Tabelle drei Zeiger: einen Zeiger auf Circle :: draw (), einen Zeiger auf Circle :: move () und einen Zeiger auf Circle :: resize ().

Während eines Versands einer virtuellen Funktion folgt das Laufzeitsystem dem V-Zeiger des Objekts auf die V-Tabelle der Klasse und folgt dann dem entsprechenden Slot in der V-Tabelle zum Methodencode.

Der Platzaufwand für die oben genannte Technik ist nominal: ein zusätzlicher Zeiger pro Objekt (jedoch nur für Objekte, für die eine dynamische Bindung erforderlich ist) sowie ein zusätzlicher Zeiger pro Methode (jedoch nur für virtuelle Methoden). Der Zeitaufwand ist ebenfalls relativ gering: Im Vergleich zu einem normalen Funktionsaufruf erfordert ein virtueller Funktionsaufruf zwei zusätzliche Abrufe (einen zum Abrufen des Werts des V-Zeigers und einen zweiten zum Abrufen der Adresse der Methode). Keine dieser Laufzeitaktivitäten findet bei nicht virtuellen Funktionen statt, da der Compiler nicht virtuelle Funktionen ausschließlich zur Kompilierungszeit basierend auf dem Typ des Zeigers auflöst.


Mein Problem oder wie ich hierher gekommen bin

Ich versuche jetzt, so etwas für eine Cubefile-Basisklasse mit vorlagenoptimierten Ladefunktionen zu verwenden, die für verschiedene Arten von Cubes unterschiedlich implementiert werden (einige nach Pixel, andere nach Bild usw.).

Etwas Code:

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;

Was ich gerne hätte, aber es wird aufgrund einer Kombination aus virtuellen Vorlagen nicht kompiliert:

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;

Am Ende habe ich die Vorlagendeklaration auf die Klassenebene verschoben . Diese Lösung hätte Programme gezwungen, bestimmte Datentypen zu kennen, die sie lesen würden, bevor sie sie lesen, was nicht akzeptabel ist.

Lösung

Warnung, das ist nicht sehr hübsch, aber es hat mir erlaubt, sich wiederholenden Ausführungscode zu entfernen

1) in der Basisklasse

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) und in den Kinderklassen

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

Beachten Sie, dass LoadAnyCube in der Basisklasse nicht deklariert ist.


Hier ist eine weitere Antwort zum Stapelüberlauf mit einer Problemumgehung: Sie benötigen eine Problemumgehung für virtuelle Vorlagenmitglieder .

Mark Essel
quelle
1
Ich traf die gleiche Situation und die Vererbungsstruktur von Massenklassen. Makros halfen.
ZFY
16

Der folgende Code kann mit MinGW G ++ 3.4.5 unter Windows 7 kompiliert und ordnungsgemäß ausgeführt werden:

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

und die Ausgabe ist:

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

Und später habe ich eine neue Klasse X hinzugefügt:

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

Als ich versuchte, Klasse X in main () wie folgt zu verwenden:

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

g ++ meldet den folgenden Fehler:

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

Es ist also offensichtlich, dass:

  • Die virtuelle Elementfunktion kann in einer Klassenvorlage verwendet werden. Für den Compiler ist es einfach, vtable zu erstellen
  • Es ist unmöglich, eine Klassenvorlagenelementfunktion als virtuell zu definieren. Wie Sie sehen, ist es schwierig, die Funktionssignatur zu bestimmen und vtable-Einträge zuzuweisen.
Brent81
quelle
19
Eine Klassenvorlage kann virtuelle Elementfunktionen haben. Eine Mitgliedsfunktion ist möglicherweise nicht gleichzeitig eine Mitgliedsfunktionsvorlage und eine virtuelle Mitgliedsfunktion.
James McNellis
1
es schlägt tatsächlich mit gcc 4.4.3 fehl. Auf meinem System sicher Ubuntu 10.04
Blueskin
3
Dies ist völlig anders als in der Frage. Hier wird die gesamte Basisklasse als Vorlage verwendet. Ich habe so etwas schon einmal zusammengestellt. Dies würde auch auf Visual Studio 2010 kompiliert werden
ds-bos-msk
14

Nein, können sie nicht. Aber:

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

hat fast den gleichen Effekt, wenn Sie nur eine gemeinsame Schnittstelle haben und die Implementierung auf Unterklassen verschieben möchten.

Tom
quelle
3
Dies wird als CRTP bezeichnet, wenn jemand neugierig ist.
Michael Choi
1
Dies hilft jedoch nicht in den Fällen, in denen man eine Klassenhierarchie hat und virtuelle Zeigermethoden auf die Basisklassen aufrufen möchte. Ihr FooZeiger ist qualifiziert als Foo<Bar>, er kann nicht auf ein Foo<Barf>oder zeigen Foo<XXX>.
Kai Petzke
@KaiPetzke: Du kannst keinen uneingeschränkten Zeiger konstruieren, nein. Sie können jedoch jeden Code vorlegen, der den konkreten Typ nicht kennen muss, was fast den gleichen Effekt hat (zumindest konzeptionell - offensichtlich völlig andere Implementierung).
Tom
8

Nein, Vorlagenelementfunktionen können nicht virtuell sein.

dirkgently
quelle
9
Meine Neugier ist: Warum? Welche Probleme hat der Compiler dabei?
WannaBeGeek
1
Sie benötigen eine Deklaration im Geltungsbereich (zumindest, um die Typen korrekt zu machen). Der Standard (und die Sprache) verlangen, dass eine Erklärung zum Geltungsbereich der von Ihnen verwendeten Bezeichner vorliegt.
dirkgently
4

In den anderen Antworten ist die vorgeschlagene Vorlagenfunktion eine Fassade und bietet keinen praktischen Nutzen.

  • Vorlagenfunktionen sind nützlich, um Code nur einmal mit verschiedenen Typen zu schreiben.
  • Virtuelle Funktionen sind nützlich, um eine gemeinsame Schnittstelle für verschiedene Klassen zu haben.

Die Sprache erlaubt keine virtuellen Vorlagenfunktionen, aber mit einer Problemumgehung ist es möglich, beide zu haben, z. B. eine Vorlagenimplementierung für jede Klasse und eine virtuelle gemeinsame Schnittstelle.

Es ist jedoch erforderlich, für jede Kombination von Vorlagentypen eine virtuelle Dummy-Wrapper-Funktion zu definieren:

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

Ausgabe:

Die quadratische Fläche beträgt 1, die Kreisfläche beträgt 3,1415926535897932385

Probieren Sie es hier aus

andreaplanet
quelle
3

Um den zweiten Teil der Frage zu beantworten:

Wenn sie virtuell sein können, was ist ein Beispiel für ein Szenario, in dem man eine solche Funktion verwenden würde?

Dies ist keine unvernünftige Sache. Zum Beispiel hat Java (wo jede Methode virtuell ist) keine Probleme mit generischen Methoden.

Ein Beispiel in C ++ für den Wunsch nach einer virtuellen Funktionsvorlage ist eine Elementfunktion, die einen generischen Iterator akzeptiert. Oder eine Mitgliedsfunktion, die ein generisches Funktionsobjekt akzeptiert.

Die Lösung für dieses Problem besteht darin, die Typlöschung mit der Funktion boost :: any_range und boost :: zu verwenden, mit der Sie einen generischen Iterator oder Funktor akzeptieren können, ohne Ihre Funktion zu einer Vorlage machen zu müssen.

ausschließen
quelle
6
Java-Generika sind syntaktischer Zucker für das Casting. Sie sind nicht dasselbe wie Vorlagen.
Brice M. Dempsey
2
@ BriceM.Dempsey: Man könnte sagen, dass Casting die Art und Weise ist, wie Java Generika implementiert, und nicht umgekehrt ... und sematisch gesehen ist das Ausschließen von Anwendungsfällen IMO gültig.
Einpoklum
2

Es gibt eine Problemumgehung für die 'virtuelle Vorlagenmethode', wenn eine Reihe von Typen für die Vorlagenmethode im Voraus bekannt ist.

Um die Idee zu zeigen, werden im folgenden Beispiel nur zwei Typen verwendet ( intund double).

Dort Base::Methodruft eine 'virtuelle' Vorlagenmethode ( ) die entsprechende virtuelle Methode (eine von Base::VMethod) auf, die wiederum die Implementierung der Vorlagenmethode ( ) aufruft Impl::TMethod.

Man muss nur die Template-Methode TMethodin abgeleiteten Implementierungen ( AImpl, BImpl) implementieren und verwenden 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;
}

Ausgabe:

0
1
2
3

NB: Base::Methodist tatsächlich ein Überschuss für echten Code ( VMethodkann veröffentlicht und direkt verwendet werden). Ich habe es hinzugefügt, damit es wie eine tatsächliche "virtuelle" Vorlagenmethode aussieht.

sad1raf
quelle
Ich habe diese Lösung gefunden, als ich ein Problem bei der Arbeit gelöst habe. Es scheint Mark Essel oben ähnlich zu sein, aber ich hoffe, es ist besser implementiert und erklärt.
Sad1raf
Ich würde dies bereits als Code-Verschleierung qualifizieren, und Sie umgehen immer noch nicht die Tatsache, dass Sie die ursprüngliche BaseKlasse jedes Mal ändern müssen, wenn Sie eine Vorlagenfunktion mit einem Argumenttyp aufrufen müssen, der nicht mit den bisher implementierten kompatibel ist. Diese Notwendigkeit zu vermeiden ist die Absicht von Vorlagen ...
Aconcagua
Essels Ansatz ist völlig anders: Gewöhnliche virtuelle Funktionen, die unterschiedliche Vorlageninstanziierungen akzeptieren - und die endgültige Vorlagenfunktion in der abgeleiteten Klasse dient nur dazu, Codeduplikationen zu vermeiden, und hat nicht einmal ein Gegenstück in der Basisklasse ...
Aconcagua
2

Während eine ältere Frage, die von vielen beantwortet wurde, meiner Meinung nach eine prägnante Methode ist, die sich nicht so stark von den anderen unterscheidet, ist die Verwendung eines kleinen Makros, um das Duplizieren von Klassendeklarationen zu erleichtern.

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

Um nun unsere Unterklasse zu implementieren:

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

Der Vorteil hierbei ist, dass beim Hinzufügen eines neu unterstützten Typs alles über den abstrakten Header erfolgen kann und möglicherweise nicht in mehreren Quell- / Header-Dateien korrigiert werden kann.

mccatnm
quelle
0

Zumindest mit gcc 5.4 könnten virtuelle Funktionen Vorlagenmitglieder sein, müssen aber selbst Vorlagen sein.

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

Ausgänge

mix before a2
Process finished with exit code 0
Maxim Sinev
quelle
0

Versuche dies:

Schreiben Sie in 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);
    }

Überprüfen Sie, ob Sie damit arbeiten, um diesen Code in main.cpp zu schreiben:

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

int main()
{
     Example exmpl;
     exmpl.c_value = "Hello, world!";
     std::cout << exmpl.VirtualFunc(exmpl);
     return 0;
}
GobeRadJem32
quelle