Warum können wir keinen std :: vector <AbstractClass> deklarieren?

88

Nachdem ich einige Zeit in C # verbracht hatte, stellte ich fest, dass Sie, wenn Sie eine abstrakte Klasse deklarieren, um sie als Schnittstelle zu verwenden, keinen Vektor dieser abstrakten Klasse instanziieren können, um Instanzen der untergeordneten Klassen zu speichern.

#pragma once
#include <iostream>
#include <vector>

using namespace std;

class IFunnyInterface
{
public:
    virtual void IamFunny()  = 0;
};

class FunnyImpl: IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        cout << "<INSERT JOKE HERE>";
    }
};

class FunnyContainer
{
private:
    std::vector <IFunnyInterface> funnyItems;
};

Die Zeile, die den Vektor der abstrakten Klasse deklariert, verursacht diesen Fehler in MS VS2005:

error C2259: 'IFunnyInterface' : cannot instantiate abstract class

Ich sehe eine offensichtliche Problemumgehung, die darin besteht, IFunnyInterface durch Folgendes zu ersetzen:

class IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        throw new std::exception("not implemented");
    }
};

Ist dies eine akzeptable Problemumgehung für C ++? Wenn nicht, gibt es eine Drittanbieter-Bibliothek wie Boost, die mir helfen könnte, dies zu umgehen?

Vielen Dank für das Lesen!

Anthony

BlueTrin
quelle

Antworten:

125

Sie können abstrakte Klassen nicht instanziieren, daher kann ein Vektor abstrakter Klassen nicht funktionieren.

Sie können jedoch einen Vektor von Zeigern auf abstrakte Klassen verwenden:

std::vector<IFunnyInterface*> ifVec;

Auf diese Weise können Sie auch polymorphes Verhalten verwenden - selbst wenn die Klasse nicht abstrakt wäre, würde das Speichern nach Wert zum Problem des Objektschneidens führen .

Georg Fritzsche
quelle
5
oder Sie können std :: vector <std :: tr1 :: shared_ptr <IFunnyInterface>> verwenden, wenn Sie die Objektlebensdauer nicht manuell behandeln möchten.
Sergey Teplyakov
4
Oder noch besser, boost :: ptr_vector <>.
Roel
7
Oder jetzt std :: vector <std :: unique_ptr <IFunnyInterface>>.
Kaz Dragon
21

Sie können keinen Vektor eines abstrakten Klassentyps erstellen, da Sie keine Instanzen einer abstrakten Klasse und C ++ - Standardbibliothekscontainer wie std :: vector-Speicherwerte (dh Instanzen) erstellen können. Wenn Sie dies tun möchten, müssen Sie einen Vektor von Zeigern auf den abstrakten Klassentyp erstellen.

Ihr Workround würde nicht funktionieren, da virtuelle Funktionen (weshalb Sie die abstrakte Klasse an erster Stelle haben möchten) nur funktionieren, wenn sie über Zeiger oder Referenzen aufgerufen werden. Sie können auch keine Referenzvektoren erstellen. Dies ist ein zweiter Grund, warum Sie einen Zeigervektor verwenden müssen.

Sie sollten erkennen, dass C ++ und C # sehr wenig gemeinsam haben. Wenn Sie C ++ lernen möchten, sollten Sie sich vorstellen, dass Sie bei Null anfangen, und ein gutes dediziertes C ++ - Tutorial wie Accelerated C ++ von Koenig und Moo lesen .


quelle
Vielen Dank, dass Sie zusätzlich zur Beantwortung des Beitrags ein Buch empfohlen haben!
BlueTrin
Wenn Sie jedoch einen Vektor abstrakter Klassen deklarieren, fordern Sie ihn nicht auf, eine abstrakte Klasse zu erstellen, sondern nur einen Vektor, der eine nicht abstrakte Unterklasse dieser Klasse enthalten kann? Wenn Sie dem Vektorkonstruktor keine Zahl übergeben, wie kann er dann möglicherweise wissen, wie viele Instanzen der abstrakten Klasse erstellt werden sollen?
Jonathan.
6

In diesem Fall können wir nicht einmal diesen Code verwenden:

std::vector <IFunnyInterface*> funnyItems;

oder

std::vector <std::tr1::shared_ptr<IFunnyInterface> > funnyItems;

Da zwischen FunnyImpl und IFunnyInterface keine IS A-Beziehung besteht und aufgrund der privaten Vererbung keine implizite Konvertierung zwischen FUnnyImpl und IFunnyInterface erfolgt.

Sie sollten Ihren Code wie folgt aktualisieren:

class IFunnyInterface
{
public:
    virtual void IamFunny()  = 0;
};

class FunnyImpl: public IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        cout << "<INSERT JOKE HERE>";
    }
};
Sergey Teplyakov
quelle
1
Die meisten Leute haben sich die private Erbschaft angesehen, denke ich :) Aber lassen Sie uns das OP nicht noch mehr verwirren :)
Roel
1
Ja. Besonders nach dem Satz des Themenstarters: "Ich habe einige Zeit damit verbracht, mich in C # zu entwickeln" (wo es überhaupt keine private Vererbung gibt).
Sergey Teplyakov
6

Die traditionelle Alternative ist die Verwendung eines vectorZeigers, wie bereits erwähnt.

Für diejenigen, die es zu schätzen wissen, gibt es Boosteine sehr interessante Bibliothek: Sie Pointer Containersist perfekt für die Aufgabe geeignet und befreit Sie von den verschiedenen Problemen, die Zeiger mit sich bringen:

  • Lebenszeitmanagement
  • doppelte Dereferenzierung von Iteratoren

Beachten Sie, dass dies vectorsowohl hinsichtlich der Leistung als auch der Benutzeroberfläche erheblich besser ist als bei intelligenten Zeigern.

Jetzt gibt es eine dritte Alternative, die darin besteht, Ihre Hierarchie zu ändern. Zur besseren Isolierung des Benutzers habe ich mehrmals das folgende verwendete Muster gesehen:

class IClass;

class MyClass
{
public:
  typedef enum { Var1, Var2 } Type;

  explicit MyClass(Type type);

  int foo();
  int bar();

private:
  IClass* m_impl;
};

struct IClass
{
  virtual ~IClass();

  virtual int foo();
  virtual int bar();
};

class MyClass1: public IClass { .. };
class MyClass2: public IClass { .. };

Dies ist recht einfach und eine Variation der PimplRedewendung, die durch ein StrategyMuster angereichert wird .

Dies funktioniert natürlich nur in dem Fall, in dem Sie die "wahren" Objekte nicht direkt bearbeiten möchten, und erfordert eine Tiefenkopie. Es kann also nicht das sein, was Sie wünschen.

Matthieu M.
quelle
1
Vielen Dank für die Boost-Referenz und das Design-Muster
BlueTrin
2

Um die Größe eines Vektors zu ändern, müssen Sie den Standardkonstruktor und die Größe der Klasse verwenden, was wiederum erfordert, dass er konkret ist.

Sie können einen Zeiger wie vorgeschlagen verwenden.

kennytm
quelle
1

std :: vector versucht, Speicher zuzuweisen, der Ihren Typ enthält. Wenn Ihre Klasse rein virtuell ist, kann der Vektor die Größe der Klasse, die er zuordnen muss, nicht kennen.

Ich denke, dass Sie mit Ihrer vector<IFunnyInterface>Problemumgehung in der Lage sein werden, eine zu kompilieren, aber Sie werden FunnyImpl darin nicht manipulieren können. Wenn beispielsweise IFunnyInterface (abstrakte Klasse) die Größe 20 hat (ich weiß es nicht wirklich) und FunnyImpl die Größe 30 hat, weil es mehr Mitglieder und Code hat, werden Sie am Ende versuchen, 30 in Ihren Vektor von 20 zu passen

Die Lösung wäre, Speicher auf dem Heap mit "neu" zuzuweisen und Zeiger darin zu speichern vector<IFunnyInterface*>

Eric
quelle
Ich dachte, dies sei die Antwort, aber suchen Sie nach gf-Antwort und Objekt-Slicing, es erklärt genau, was innerhalb des Containers passieren wird
BlueTrin
Diese Antwort beschreibt, was passieren würde, ohne das Wort "Schneiden" zu verwenden, daher ist diese Antwort korrekt. Bei Verwendung eines Vektors von ptrs erfolgt kein Schneiden. Das ist der springende Punkt bei der Verwendung von ptrs.
Roel
-2

Ich denke, dass die Hauptursache für diese wirklich traurige Einschränkung die Tatsache ist, dass Konstruktoren nicht virtuell sein können. Der Compiler kann keinen Code generieren, der das Objekt kopiert, ohne seine Zeit in der Kompilierungszeit zu kennen.

David Gruzman
quelle
2
Dies ist nicht die Grundursache und keine "traurige Einschränkung".
Bitte erläutern Sie, warum dies Ihrer Meinung nach keine Einschränkung darstellt. Es wäre schön, die Fähigkeit zu haben. Der Programmierer hat einen gewissen Aufwand, wenn er gezwungen ist, Zeiger auf den Container zu setzen und sich über eine Löschung Gedanken zu machen. Ich bin damit einverstanden, dass Objekte unterschiedlicher Größe im selben Behälter die Leistung beeinträchtigen.
David Gruzman
Der Versand virtueller Funktionen basiert auf dem Typ Ihres Objekts. Der springende Punkt bei Konstruktoren ist, dass sie noch kein Objekt haben . Bezogen auf den Grund, warum Sie keine statischen virtuellen Funktionen haben können: auch kein Objekt.
MSalters
Ich kann sagen, dass die Vorlage des Klassencontainers kein Objekt benötigt, sondern eine Klassenfabrik, und der Konstruktor ist ein natürlicher Bestandteil davon.
David Gruzman