it-swarm.com.de

C++ Ein std :: vector, der eine Schablonenklasse mehrerer Typen enthält

Ich muss mehrere Typen einer Vorlagenklasse in einem einzigen Vektor speichern.

ZB für:

template <typename T>
class templateClass{
     bool someFunction();
};

Ich brauche einen Vektor, der alles speichert:

templateClass<int> t1;
templateClass<char> t2;
templateClass<std::string> t3;
etc

Soweit ich weiß, ist dies nicht möglich, wenn jemand wie sagen könnte?

Wenn es nicht möglich ist, kann jemand erklären, wie die folgende Arbeit ausgeführt wird?

Um dies zu umgehen, habe ich versucht, eine Basis-Klasse zu verwenden, die keine Vorlageklasse ist, und die Vorlagenklasse daraus erben.

 class templateInterface{
     virtual bool someFunction() = 0;
 };

 template <typename T>
 class templateClass : public templateInterface{
     bool someFunction();
 };

Ich habe dann einen Vektor erstellt, um die Basisklasse "templateInterface" zu speichern:

std::vector<templateInterface> v;
templateClass<int> t;
v.Push_back(t);

Dies erzeugte den folgenden Fehler:

error: cannot allocate an object of abstract type 'templateInterface'
note: because the following virtual functions are pure within 'templateInterface'
note: virtual bool templateInterface::someFunction()

Um diesen Fehler zu beheben, habe ich die Funktion in templateInterface nicht zu einem reinen virtuellen Objekt gemacht, indem ein Funktionskörper bereitgestellt wurde. Dieser wurde kompiliert. Beim Aufruf der Funktion wird jedoch nicht die Overide verwendet, sondern der Körper in der virtuellen Funktion.

Z.B:

 class templateInterface{
     virtual bool someFunction() {return true;}
 };

 template <typename T>
 class templateClass : public templateInterface{
     bool someFunction() {return false;}
 };

 std::vector<templateInterface> v;
 templateClass<int> i;
 v.Push_back(i);
 v[0].someFunction(); //This returns true, and does not use the code in the 'templateClass' function body

Gibt es eine Möglichkeit, das Problem so zu beheben, dass die überschriebene Funktion verwendet wird, oder gibt es eine andere Problemumgehung, um mehrere Vorlagentypen in einem einzigen Vektor zu speichern?

34
jtedit

Warum funktioniert dein Code nicht:

Beim Aufruf einer virtuellen Funktion für einen Wert wird kein Polymorphismus verwendet. Es ruft die Funktion auf, die für den Typ dieses exakten Symbols definiert ist, wie er vom Compiler gesehen wird, nicht den Laufzeittyp. Wenn Sie Untertypen in einen Vektor des Basistyps einfügen, werden Ihre Werte konvertiert in den Basistyp ("Typschnitt") übernommen, was Sie nicht möchten. Beim Aufruf von Funktionen wird nun die Funktion wie für den Basistyp definiert aufgerufen, da es nicht is dieses Typs ist.

Wie kann ich das beheben?

Dasselbe Problem kann mit diesem Code-Snippet reproduziert werden:

templateInterface x = templateClass<int>(); // Type slicing takes place!
x.someFunction();  // -> templateInterface::someFunction() is called!

Polymorphie funktioniert nur bei einem Zeiger oder Verweis Typ. Anschließend wird der Laufzeittyp des Objekts hinter dem Zeiger/der Referenz verwendet, um zu entscheiden, welche Implementierung aufgerufen werden soll (mithilfe der vtable).

Das Konvertieren von Zeigern ist in Bezug auf das Typ-Slicing absolut "sicher". Ihre actual - Werte werden nicht konvertiert und der Polymorphismus funktioniert wie erwartet.

Beispiel analog zum obigen Code-Snippet:

templateInterface *x = new templateClass<int>();  // No type slicing takes place
x->someFunction();  // -> templateClass<int>::someFunction() is called!

delete x;  // Don't forget to destroy your objects.

Was ist mit Vektoren?

Sie müssen diese Änderungen also in Ihren Code übernehmen. Sie können Zeiger einfach zu tatsächlichen Typen im Vektor speichern, anstatt die Werte direkt zu speichern.

Wenn Sie mit Zeigern arbeiten, müssen Sie auch die zugewiesenen Objekte löschen. Hierfür können Sie intelligente Zeiger verwenden, die automatisch gelöscht werden. unique_ptr ist ein solcher intelligenter Zeigertyp. Es löscht den Pointee, wenn er den Gültigkeitsbereich verlässt ("unique owner" - der Geltungsbereich ist der Eigentümer). Angenommen, die Lebensdauer Ihrer Objekte ist an den Geltungsbereich gebunden, sollten Sie Folgendes verwenden:

std::vector<std::unique_ptr<templateInterface>> v;

templateClass<int> *i = new templateClass<int>();    // create new object
v.Push_back(std::unique_ptr<templateInterface>(i));  // put it in the vector

v.emplace_back(new templateClass<int>());   // "direct" alternative

Rufen Sie dann eine virtuelle Funktion für eines dieser Elemente mit der folgenden Syntax auf:

v[0]->someFunction();

Stellen Sie sicher, dass Sie alle Funktionen virtual erstellen, die von Unterklassen überschrieben werden können. Andernfalls wird die überschriebene Version nicht aufgerufen. Aber da Sie bereits eine "Schnittstelle" eingeführt haben, arbeiten Sie mit abstrakten Funktionen.

Alternative Ansätze:

Alternative Methoden, um das zu tun, was Sie möchten, ist die Verwendung eines Typs Variante im Vektor. Es gibt einige Implementierungen von Variantentypen, wobei Boost.Variant sehr beliebt ist. Dieser Ansatz ist besonders nett, wenn Sie keine Typhierarchie haben (z. B. wenn Sie primitive Typen speichern). Sie würden dann einen Vektortyp wie std::vector<boost::variant<int, char, bool>> verwenden.

26
leemes

Polymorphismus funktioniert nur durch Zeiger oder Verweise. Sie benötigen Die Nicht-Vorlagenbasis. Darüber hinaus müssen Sie entscheiden, Wo sich die eigentlichen Objekte im Container befinden. Wenn es sich um statische Objekte (mit ausreichender Lebensdauer) handelt, sollten Sie einfach std::vector<TemplateInterface*> verwenden und mit v.Push_back(&t1); einfügen usw. den Trick ausführen. Andernfalls möchten Sie wahrscheinlich das Klonen unterstützen und Klone im Vektor __. behalten: vorzugsweise mit Boost-Pointer-Containern, aber auch _std::shared_ptr kann verwendet werden. 

2
James Kanze

Die Lösungen, die bisher gegeben wurden, sind in Ordnung, obwohl Sie wissen, dass in dem Fall, dass Sie den Vorlagentyp anders als bool zurückgegeben haben, keine davon helfen würde, da die vtable-Slots nicht vorab gemessen werden könnten. In Bezug auf das Design gibt es tatsächlich Grenzen für die Verwendung einer templatorientierten polymorphen Lösung.

2

Wenn Sie sich einen Container ansehen, um mehrere Typen zu speichern, sollten Sie boost-Variante aus der beliebten Boost-Bibliothek untersuchen.

1
jbo5112

Lösung nr. 1

Diese Lösung wurde von Sean Parents C++ Seasoning Talk inspiriert. Ich empfehle es auf Youtube. Meine Lösung wurde ein wenig vereinfacht und der Schlüssel besteht darin, das Objekt in der Methode selbst zu speichern.

Nur eine Methode

Erstellen Sie eine Klasse, die die Methode des gespeicherten Objekts aufruft.

struct object {
    template <class T>
    object(T t)
    : someFunction([t = std::move(t)]() { return t.someFunction(); })
    { }

    std::function<bool()> someFunction;
};

Dann benutze es so

std::vector<object> v;

// Add classes that has 'bool someFunction()' method
v.emplace_back(someClass());
v.emplace_back(someOtherClass());

// Test our vector
for (auto& x : v)
    std::cout << x.someFunction() << std::endl;

Mehrere Methoden

Verwenden Sie für mehrere Methoden den gemeinsam genutzten Zeiger, um Objekte zwischen Methoden zu teilen

struct object {
    template <class T>
    object(T&& t) {
        auto ptr = std::make_shared<std::remove_reference_t<T>>(std::forward<T>(t));
        someFunction = [ptr]() { return ptr->someFunction(); };
        someOtherFunction = [ptr](int x) { ptr->someOtherFunction(x); };
    }

    std::function<bool()> someFunction;
    std::function<void(int)> someOtherFunction;
};

Andere Arten

Primitive Typen (wie int, float, const char*) oder Klassen (std::string usw.) können auf dieselbe Weise wie object-Klassen umhüllt werden, verhalten sich jedoch anders. Zum Beispiel: 

struct otherType {
    template <class T>
    otherType(T t)
    : someFunction([t = std::move(t)]() {
            // Return something different
            return true;
        })
    { }

    std::function<bool()> someFunction;
};

Es ist jetzt möglich, Typen hinzuzufügen, die keine someFunction-Methode haben.

v.emplace_back(otherType(17));      // Adding an int
v.emplace_back(otherType("test"));  // A string

Lösung nr. 2

Nach einigen Überlegungen, was wir in der ersten Lösung gemacht haben, wird ein Array von aufrufbaren Funktionen erstellt. Warum also nicht einfach folgendes tun?.

// Example class with method we want to put in array
struct myclass {
    void draw() const {
        std::cout << "myclass" << std::endl;
    }
};

// All other type's behaviour
template <class T>
void draw(const T& x) {
    std::cout << typeid(T).name() << ": " << x << std::endl;
}

int main()
{
    myclass x;
    int y = 17;

    std::vector<std::function<void()>> v;

    v.emplace_back(std::bind(&myclass::draw, &x));
    v.emplace_back(std::bind(draw<int>, y));

    for (auto& fn : v)
        fn();
}

Fazit

Lösung nr. 1 ist definitiv eine interessante Methode, die weder Vererbung noch virtuelle Funktionen erfordert. Und kann für andere Dinge verwendet werden, bei denen Sie ein Vorlagenargument speichern müssen, das später verwendet werden kann.

Lösung nr. 2 dagegen ist einfacher, flexibler und wahrscheinlich die bessere Wahl.

0