2010-01-29 18 views
61

Avendo trascorso un po 'di tempo sviluppando in C#, ho notato che se si dichiara una classe astratta allo scopo di usarlo come interfaccia non è possibile creare un'istanza di un vettore di questa classe astratta per memorizzare istanze delle classi secondarie.Perché non possiamo dichiarare uno std :: vector <AbstractClass>?

#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 linea che dichiara il vettore di classe astratta causa questo errore in MS VS2005:

error C2259: 'IFunnyInterface' : cannot instantiate abstract class 

vedo una soluzione ovvia, che sostituirà IFunnyInterface con il seguente:

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

È una soluzione accettabile C++ saggia? In caso contrario, c'è qualche spinta di libreria di terze parti che potrebbe aiutarmi a risolvere questo problema?

Grazie per aver letto questo!

Anthony

risposta

92

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

È possibile comunque utilizzare un vettore di puntatori a classi astratte:

std::vector<IFunnyInterface*> ifVec; 

Questo permette anche di utilizzare effettivamente il comportamento polimorfico - anche se la classe non era astratta, la conservazione per valore porterebbe al problema di object slicing.

+10

+1 per menzionare l'affettatura, anche – xtofl

+0

Grazie per la risposta – BlueTrin

+4

oppure è possibile utilizzare std :: vector > se non si desidera gestire manualmente la durata dell'oggetto. –

17

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

Il vostro workround non funzionerebbe perché le funzioni virtuali (che è il motivo per cui si desidera la classe astratta in primo luogo) funzionano solo se chiamate tramite puntatori o riferimenti. Non è possibile creare nemmeno vettori di riferimenti, quindi questa è una seconda ragione per cui è necessario utilizzare un vettore di puntatori.

Si dovrebbe capire che C++ e C# hanno molto poco in comune. Se hai intenzione di imparare il C++, dovresti pensare a come iniziare da zero e leggere un buon tutorial C++ dedicato come Accelerated C++ di Koenig e Moo.

+0

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

+0

+1 Mi hai salvato due ore su questo problema, grazie. –

+0

Ma quando si dichiara un vettore di classi astratte, non si sta chiedendo di creare alcuna classe astratta, solo un vettore che sia in grado di contenere una sottoclasse non astratta di quella classe? A meno che non si passi un numero al costruttore dei vettori, come può sapere quante delle istanze della classe astratta creare? –

2

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

È possibile utilizzare un puntatore come suggerito da altri.

-2

Penso che la causa principale di questa limitazione davvero triste sia il fatto che i costruttori non possono virtualizzare. Di conseguenza, il compilatore non può generare codice che copia l'oggetto senza conoscerne l'ora nel tempo di compilazione.

+0

+1 per spiegare perché succede l'affettamento degli oggetti – BlueTrin

+2

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

+0

Si prega di spiegare perché pensi che non sia una limitazione? Sarebbe bello avere la capacità.E c'è un sovraccarico sul programmatore quando lui/lei è costretto a mettere dei puntatori al contenitore, e preoccuparsi di una cancellazione. Sono d'accordo sul fatto che avere oggetti di dimensioni diverse nello stesso contenitore danneggerebbe le prestazioni. –

1

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

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

Il soluzione sarebbe quella di allocare la memoria sul mucchio con "nuovi" e memorizzare i puntatori in vector<IFunnyInterface*>

+0

Ho pensato che fosse la risposta, ma cerco la risposta gf e l'affettamento degli oggetti, spiega esattamente cosa accadrà all'interno del contenitore – BlueTrin

+0

Questa risposta descrive cosa succederebbe ma senza usare la parola "affettare", quindi questa risposta è corretta. Quando si utilizza un vettore di ptr, non si verificherà alcuna divisione. Questo è il punto principale dell'utilizzo di ptrs in primo luogo. – Roel

5

in questo caso non possiamo usare anche 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 esiste una conversione implicita tra FUnnyImpl e IFunnyInterface a causa dell'ereditarietà privata.

Si consiglia di 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 guardato l'eredità privata penso :) Ma non confondiamo l'OP ancora di più :) – Roel

+1

Sì. Soprattutto dopo la frase introduttiva di argomento: "Avendo trascorso un po 'di tempo sviluppando in C#" (dove nessuna eredità privata). –

6

L'alternativa tradizionale è quello di utilizzare un vector di puntatori, come già notato.

Per coloro che apprezzano, Boost viene fornito con una libreria molto interessante: Pointer Containers che è perfettamente adatto per il compito e libera dai vari problemi implicati da puntatori:

  • gestione della durata
  • doppia dereferenziazione di iteratori

Si noti che questo è significativamente migliore di uno vector di puntatori intelligenti, sia in termini di prestazioni che di interfaccia.

Ora, c'è una terza alternativa, che è quella di cambiare la gerarchia. Per un migliore isolamento dell'utente, ho visto un certo numero di 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 è molto semplice, e una variazione del Pimpl linguaggio arricchito da un modello Strategy.

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

+1

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

Problemi correlati