it-swarm.com.de

Kann eine C++ - Klassenmitgliedsfunktionsvorlage virtuell sein?

Ich habe gehört, dass C++ - Klassenmitgliedsfunktionsvorlagen nicht virtuell sein können. Ist das wahr? 

Wenn sie virtuell sein können, was ist ein Beispiel für ein Szenario, in dem eine solche Funktion verwendet wird?

261
WannaBeGeek

Bei Vorlagen geht es darum, dass der Compiler zum Zeitpunkt compile-time Code generiert. Bei virtuellen Funktionen dreht sich alles um das Laufzeitsystem, um herauszufinden, welche Funktion um Laufzeit aufgerufen werden soll. 

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

Es gibt jedoch einige leistungsstarke und interessante Techniken, die aus der Kombination von Polymorphismus und Templates stammen, insbesondere der sogenannten type erasure

289
sbi

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

Mitgliedsfunktionsvorlagen können nicht als virtuell deklariert werden. Diese Einschränkung wird auferlegt, weil die übliche Implementierung der virtuellen Funktion Der Aufrufmechanismus verwendet eine Tabelle mit fester Größe mit einem Eintrag pro virtuellen Funktion. Die Anzahl der Instanziierungen einer Member-Funktion Die Vorlage ist nicht festgelegt, bis das gesamte Programm übersetzt wurde . Für die Unterstützung von Funktionsschablonen für virtuelle Member wäre daher __ erforderlich. Unterstützung für eine völlig neue Art von Mechanismus in C++ - Compilern und Linker. Im Gegensatz dazu können normale Mitglieder von Klassenvorlagen .__ sein. virtuell, weil ihre Anzahl festgelegt ist, wenn eine Klasse instanziiert wird

104
InQusitive

C++ lässt derzeit keine virtuellen Template-Member-Funktionen zu. Der wahrscheinlichste Grund ist die Komplexität der Implementierung. Rajendra gibt einen guten Grund, warum dies nicht jetzt möglich ist, aber es könnte mit vernünftigen Änderungen des Standards möglich sein. Insbesondere die Ermittlung, wie viele Instanziierungen einer Templat-Funktion tatsächlich existieren, und der Aufbau der vtable scheint schwierig zu sein, wenn Sie den Ort des virtuellen Funktionsaufrufs berücksichtigen. Standards-Leute haben gerade eine Menge anderer Dinge zu erledigen, und C++ 1x ist auch für Compiler-Autoren eine Menge Arbeit.

Wann benötigen Sie eine Templated Member-Funktion? Ich bin einmal auf eine solche Situation gestoßen, in der ich versuchte, eine Hierarchie mit einer reinen virtuellen Basisklasse umzuformen. Es war ein schlechter Stil für die Umsetzung verschiedener Strategien. Ich wollte das Argument einer der virtuellen Funktionen in einen numerischen Typ ändern. Statt die Member-Funktion zu überladen und jede Überladung in allen Unterklassen zu überschreiben, versuchte ich, virtuelle Template-Funktionen zu verwenden (und musste feststellen, dass sie nicht existieren.) .) 

31
pmr

Virtuelle Funktionstabellen

Beginnen wir mit etwas Hintergrundwissen zu virtuellen Funktionstabellen und ihrer Funktionsweise ( source ):

[20.3] Was ist der Unterschied zwischen virtuellen und nicht virtuellen Elementfunktionen werden aufgerufen?

Nicht-virtuelle Memberfunktionen werden statisch aufgelöst. Das heißt, die Die Elementfunktion wird statisch (zur Kompilierzeit) auf der Grundlage von .__ ausgewählt. Typ des Zeigers (oder der Referenz) auf das Objekt.

Im Gegensatz dazu werden virtuelle Elementfunktionen dynamisch aufgelöst (um Laufzeit). Das heißt, die Elementfunktion wird dynamisch (um Laufzeit) basierend auf dem Typ des Objekts ausgewählt, nicht dem Typ von Zeiger/Verweis auf dieses Objekt. Dies wird als "dynamische Bindung" bezeichnet. Die meisten Compiler verwenden eine Variante der folgenden Technik: if Objekt hat eine oder mehrere virtuelle Funktionen, der Compiler setzt eine verborgene Zeiger im Objekt, der als "virtueller Zeiger" oder "V-Zeiger" bezeichnet wird. Diese Der V-Zeiger zeigt auf eine globale Tabelle, die als "virtuelle Tabelle" oder .__ bezeichnet wird. "v-table."

Der Compiler erstellt für jede Klasse, die mindestens ein .__ hat, eine V-Tabelle. virtuelle Funktion. Zum Beispiel, wenn die Klasse Circle virtuelle Funktionen hat Für draw () und move () und resize () gibt es genau eine V-Tabelle der Klasse Circle zugeordnet, auch wenn es einen Gazillion Circle gab Objekte und der V-Zeiger jedes dieser Circle-Objekte würde auf .__ zeigen. zum Circle V-Tisch. Die v-Tabelle selbst hat Zeiger auf jedes der virtuelle Funktionen in der Klasse. Zum Beispiel würde die Circle-V-Tabelle haben drei Zeiger: einen Zeiger auf Circle :: draw (), einen Zeiger auf Circle :: move () und einen Zeiger auf Circle :: resize ().

Während einer Übergabe einer virtuellen Funktion folgt das Laufzeitsystem der V-Zeiger des Objekts auf die V-Tabelle der Klasse, dann folgt die entsprechenden Steckplatz in der V-Tabelle zum Methodencode.

Der Platzaufwand für die obige Technik ist nominell: eine zusätzliche Zeiger pro Objekt (jedoch nur für Objekte, für die eine dynamische Bindung erforderlich ist), plus einen zusätzlichen Zeiger pro Methode (jedoch nur für virtuelle Methoden). Der Zeitaufwand ist auch ziemlich nominell: im Vergleich zu einem Normaler Funktionsaufruf, ein virtueller Funktionsaufruf erfordert zwei zusätzliche ruft ab (eins, um den Wert des V-Pointers zu erhalten, ein zweites, um die -Adresse der Methode zu erhalten). Keine dieser Laufzeitaktivitäten geschieht mit nicht virtuelle Funktionen, da der Compiler nicht virtuelle .__ auflöst. Funktionen ausschließlich zur Kompilierzeit basierend auf dem Typ der Zeiger.


Mein Problem oder wie ich hierher gekommen bin

Ich versuche, so etwas nun für eine Cubefile-Basisklasse mit optimierten Ladefunktionen mit Templates zu verwenden, die für verschiedene Arten von Cubes (einige werden nach Pixel, einige nach Abbild usw. gespeichert) unterschiedlich implementiert.

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 wäre, aber es wird nicht kompiliert aufgrund einer virtuellen Templat-Kombination:

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 verschob ich die Template-Deklaration auf Klassenebene. Diese Lösung hätte Programme dazu gezwungen, bestimmte Datentypen zu kennen, die sie lesen würden, bevor sie sie lesen, was inakzeptabel ist.

Lösung

Warnung, das ist nicht sehr hübsch, aber ich konnte mich wiederholenden Ausführungscode 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 im Kinderunterricht

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 nicht in der Basisklasse deklariert ist. 


Hier ist eine weitere Stapelüberlauf-Antwort mit einer Problemumgehung: Sie benötigen einen Workaround für ein virtuelles Vorlagenmitglied

16
Mark Essel

Nein, das 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);
};

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

11
Tom

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

Wenn ich versucht habe, Klasse X in main () so zu verwenden:

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

g ++ meldet 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 Memberfunktion kann in einer Klassenvorlage verwendet werden. Es ist für den Compiler leicht, eine vtable zu erstellen
  • Es ist nicht möglich, eine Klassenvorlagen-Member-Funktion als virtuell zu definieren. Wie Sie sehen, ist es schwierig, die Funktionssignatur zu bestimmen und vtable-Einträge zuzuordnen.
11
Brent81

Nein, Vorlagenmitgliedsfunktionen können nicht virtuell sein. 

4
dirkgently

Um den zweiten Teil der Frage zu beantworten:

Wenn sie virtuell sein können, was ist ein Beispiel für ein Szenario, in dem eine solche Funktion verwendet wird?

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

Ein Beispiel für eine virtuelle Funktionsvorlage in C++ ist eine Memberfunktion, die einen generischen Iterator akzeptiert. Oder eine Memberfunktion, die ein generisches Funktionsobjekt akzeptiert.

Die Lösung für dieses Problem besteht in der Verwendung der Typenlöschung mit boost :: any_range und boost :: function, die es Ihnen ermöglicht, einen generischen Iterator oder -Funktionscode zu akzeptieren, ohne dass Sie Ihre Funktion zu einer Vorlage machen müssen.

3
exclipy

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

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

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

Man muss nur die Vorlagenmethode TMethod in abgeleiteten Implementierungen (AImpl, BImpl) implementieren und Derived<*Impl> verwenden.

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::Method ist tatsächlich ein Überschuss für echten Code (VMethod kann veröffentlicht und direkt verwendet werden) . Ich habe ihn hinzugefügt, so dass es wie eine "virtuelle" Vorlagenmethode aussieht.

2
sad1raf

Zumindest mit gcc 5.4 könnten virtuelle Funktionen Template-Member sein, müssen aber selbst Template 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;
}

Ausgaben

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

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

  • Vorlagenfunktionen sind hilfreich, wenn Code nur einmal mit verschiedenen Typen von
  • 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, beides zu haben, z. eine Vorlagenimplementierung für jede Klasse und eine virtuelle gemeinsame Schnittstelle.

Es ist jedoch erforderlich, für jede Vorlagentypkombination eine 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: 

Quadratische Fläche ist 1, Kreisfläche ist 3.1415926535897932385

Probier es hier

0
andreaplanet