Perché non possiamo dichiarare un std :: vector <AbstractClass>?


89

Dopo aver trascorso un po 'di tempo a sviluppare in C #, ho notato che se dichiari una classe astratta allo scopo di usarla come interfaccia non puoi istanziare un vettore di questa classe astratta per memorizzare istanze delle classi figlie.

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

La riga che dichiara il vettore della classe astratta causa questo errore in MS VS2005:

error C2259: 'IFunnyInterface' : cannot instantiate abstract class

Vedo un'ovvia soluzione alternativa, che è sostituire IFunnyInterface con quanto segue:

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

È una soluzione alternativa accettabile per C ++? In caso contrario, esiste una libreria di terze parti come boost che potrebbe aiutarmi a aggirare questo problema?

Grazie per aver letto questo!

Anthony

Risposte:


128

Non puoi istanziare classi astratte, quindi un vettore di classi astratte non può funzionare.

Puoi comunque usare un vettore di puntatori a classi astratte:

std::vector<IFunnyInterface*> ifVec;

Ciò consente anche di utilizzare effettivamente un comportamento polimorfico: anche se la classe non fosse astratta, la memorizzazione per valore porterebbe al problema dell'affettamento degli oggetti .


5
oppure puoi usare std :: vector <std :: tr1 :: shared_ptr <IFunnyInterface>> se non vuoi gestire manualmente la durata dell'oggetto.
Sergey Teplyakov

4
O ancora meglio, boost :: ptr_vector <>.
Roel

7
O ora, std :: vector <std :: unique_ptr <IFunnyInterface>>.
Kaz Dragon

21

Non è possibile creare un vettore di un tipo di classe astratta perché non è possibile creare istanze di una classe astratta e contenitori della libreria standard C ++ come std :: vector memorizzano i valori (cioè le istanze). Se vuoi farlo, dovrai creare un vettore di puntatori al tipo di classe astratta.

Il tuo ambiente di lavoro non funzionerebbe perché le funzioni virtuali (motivo per cui vuoi la classe astratta in primo luogo) funzionano solo se chiamate tramite puntatori o riferimenti. Non puoi nemmeno creare vettori di riferimenti, quindi questa è una seconda ragione per cui devi usare un vettore di puntatori.

Dovresti renderti conto che C ++ e C # hanno molto poco in comune. Se hai intenzione di imparare il C ++, dovresti pensarlo come partire da zero e leggere un buon tutorial C ++ dedicato come Accelerated C ++ di Koenig e Moo.


Grazie per aver consigliato un libro oltre a rispondere al post!
BlueTrin

Ma quando dichiari un vettore di classi astratte, non gli stai chiedendo di creare una classe astratta, solo un vettore in grado di contenere una sottoclasse non astratta di quella classe? A meno che tu non passi un numero al costruttore di vettori, come può sapere quante delle istanze della classe astratta creare?
Jonathan.

6

In questo caso non possiamo usare nemmeno questo codice:

std::vector <IFunnyInterface*> funnyItems;

o

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

Perché non esiste una relazione IS A tra FunnyImpl e IFunnyInterface e non vi è alcuna conversione implicita tra FUnnyImpl e IFunnyInterface a causa dell'eredità privata.

È necessario aggiornare il codice come segue:

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

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

1
La maggior parte delle persone ha esaminato l'eredità privata, penso :) Ma non confondiamo ancora di più l'OP :)
Roel

1
Sì. Soprattutto dopo la frase del topic starter: "Avere trascorso un bel po 'di tempo a sviluppare in C #" (dove nessuna eredità privata).
Sergey Teplyakov

6

L'alternativa tradizionale è quella di utilizzare un vectorof pointers, come già notato.

Per chi apprezza, Boostarriva con una libreria molto interessante: Pointer Containersche si adatta perfettamente al compito e ti libera dai vari problemi impliciti dai puntatori:

  • gestione della vita
  • doppia dereferenziazione degli iteratori

Nota che questo è significativamente migliore di un vectorpuntatore intelligente, sia in termini di prestazioni che di interfaccia.

Ora, c'è una terza alternativa, che è cambiare la tua gerarchia. Per un migliore isolamento dell'utente, ho visto più volte il seguente schema utilizzato:

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

Questo è abbastanza semplice e una variazione del Pimpllinguaggio arricchita da uno Strategyschema.

Funziona, ovviamente, solo nel caso in cui non desideri manipolare direttamente gli oggetti "veri" e coinvolge la copia profonda. Quindi potrebbe non essere quello che desideri.


1
Grazie per il riferimento Boost e il modello di progettazione
BlueTrin

2

Perché per ridimensionare un vettore è necessario utilizzare il costruttore di default e la dimensione della classe, che a sua volta richiede che sia concreta.

Puoi usare un puntatore come altri suggeriti.


1

std :: vector proverà ad allocare la memoria per contenere il tuo tipo. Se la tua classe è puramente virtuale, il vettore non può conoscere la dimensione della classe che dovrà allocare.

Penso che con la tua soluzione, sarai in grado di compilare un vector<IFunnyInterface>ma non sarai in grado di manipolare FunnyImpl al suo interno. Ad esempio, se IFunnyInterface (classe astratta) è di dimensione 20 (non lo so davvero) e FunnyImpl è di dimensione 30 perché ha più membri e codice, finirai per provare a inserire 30 nel tuo vettore di 20

La soluzione sarebbe allocare la memoria sull'heap con "new" e memorizzare i puntatori in vector<IFunnyInterface*>


Pensavo che questa fosse la risposta, ma cerca la risposta della gf e l'affettatura dell'oggetto, spiega esattamente cosa accadrà all'interno del contenitore
BlueTrin

Questa risposta descriveva cosa sarebbe successo ma senza usare la parola "affettare", quindi questa risposta è corretta. Quando si utilizza un vettore di ptr, non verrà eseguita alcuna suddivisione. Questo è il punto centrale dell'utilizzo di ptr in primo luogo.
Roel

-3

Penso che la causa principale di questa triste limitazione sia il fatto che i costruttori non possono fare il virtuale. Di ciò il compilatore non può generare codice che copi l'oggetto senza conoscere il suo tempo in fase di compilazione.


2
Questa non è la causa principale e non è una "triste limitazione".

Per favore, spiega perché pensi che non sia una limitazione? Sarebbe bello avere la capacità. E c'è qualche sovraccarico sul programmatore quando è costretto a mettere i puntatori al contenitore e preoccuparsi di un'eliminazione. Sono d'accordo che avere oggetti di dimensioni diverse nello stesso contenitore comprometterà le prestazioni.
David Gruzman,

Le funzioni virtuali vengono inviate in base al tipo di oggetto che hai. L'intero punto dei costruttori è che non hanno ancora un oggetto . In relazione al motivo per cui non puoi avere funzioni virtuali statiche: anche nessun oggetto.
MSalters

Posso dire che il modello del contenitore di classi non ha bisogno di oggetti, ma di una fabbrica di classi e il costruttore ne è una parte naturale.
David Gruzman,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.