Come si dichiara un'interfaccia in C ++?


Risposte:


686

Per espandere la risposta di bradtgmurray , potresti voler fare un'eccezione all'elenco dei metodi virtuali puri della tua interfaccia aggiungendo un distruttore virtuale. Ciò consente di passare la proprietà del puntatore a un'altra parte senza esporre la classe derivata concreta. Il distruttore non deve fare nulla, perché l'interfaccia non ha membri concreti. Potrebbe sembrare contraddittorio definire una funzione sia virtuale che in linea, ma fidati di me - non lo è.

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Parent
{
    public:
        virtual ~Parent();
};

class Child : public Parent, public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

Non è necessario includere un corpo per il distruttore virtuale: si scopre che alcuni compilatori hanno problemi a ottimizzare un distruttore vuoto e si sta meglio usando il valore predefinito.


106
Virtual desctuctor ++! Questo è molto importante. Potresti anche voler includere dichiarazioni virtuali pure dell'operatore = e copiare le definizioni del costruttore per impedire al compilatore di generare automaticamente quelle per te.
xan

33
Un'alternativa a un distruttore virtuale è un distruttore protetto. Ciò disabilita la distruzione polimorfica, che può essere più appropriata in alcune circostanze. Cerca "Linea guida n. 4" in gotw.ca/publications/mill18.htm .
Fred Larson,

9
Un'altra opzione è quella di definire un puro =0distruttore virtuale ( ) con un corpo. Il vantaggio qui è che il compilatore può, in teoria, vedere che vtable non ha membri validi ora e scartarlo del tutto. Con un distruttore virtuale con un corpo, detto distruttore può essere chiamato (virtualmente) ad esempio nel mezzo della costruzione tramite un thispuntatore (quando l'oggetto costruito è ancora di Parenttipo), e quindi il compilatore deve fornire una vtable valida. Quindi se non chiami esplicitamente i distruttori virtuali thisdurante la costruzione :) puoi risparmiare sulla dimensione del codice.
Pavel Minaev il

51
Come tipico di una risposta C ++ che la risposta migliore non risponda direttamente alla domanda (anche se ovviamente il codice è perfetto), invece ottimizza la risposta semplice.
Tim

18
Non dimenticare che in C ++ 11 puoi specificare la overrideparola chiave per consentire l'argomento di compilazione e il controllo del tipo di valore restituito. Ad esempio, nella dichiarazione di Childvirtual void OverrideMe() override;
Sean del

245

Crea una classe con metodi virtuali puri. Utilizzare l'interfaccia creando un'altra classe che sovrascrive quei metodi virtuali.

Un metodo virtuale puro è un metodo di classe definito virtuale e assegnato a 0.

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Child : public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

29
dovresti avere un distruttore non fare nulla in IDemo in modo che sia definito il comportamento da fare: IDemo * p = new Child; / * qualunque cosa * / elimina p;
Evan Teran,

11
Perché il metodo OverrideMe nella classe Child è virtuale? È necessario?
Cemre,

9
@Cemre - no non è necessario, ma non fa male neanche.
PowerApp101,

11
È generalmente una buona idea mantenere la parola chiave "virtuale" ogni volta che si ignora un metodo virtuale. Sebbene non sia necessario, può rendere il codice più chiaro, altrimenti non si ha alcuna indicazione che tale metodo possa essere usato polimorficamente o addirittura esiste nella classe base.
Kevin,

27
@Kevin Tranne che overridein C ++ 11
keyser

146

L'intero motivo per cui hai una speciale categoria di tipi di interfaccia oltre alle classi di base astratte in C # / Java è perché C # / Java non supporta l'ereditarietà multipla.

C ++ supporta l'ereditarietà multipla, quindi non è necessario un tipo speciale. Una classe base astratta senza metodi non astratti (puro virtuale) è funzionalmente equivalente a un'interfaccia C # / Java.


17
Sarebbe comunque bello poter creare interfacce, salvarci dal digitare così tanto (virtuale, = 0, distruttore virtuale). Anche l'ereditarietà multipla mi sembra una pessima idea e non l'ho mai vista utilizzata in pratica, ma le interfacce sono sempre necessarie. Peccato che il comity C ++ non introduca interfacce solo perché le voglio.
Ha fatto l'

9
Ha11owed: ha interfacce. Sono chiamate classi con metodi virtuali puri e senza implementazioni di metodi.
Miles Rout

6
@doc: java.lang.Thread ha metodi e costanti che probabilmente non vuoi avere nel tuo oggetto. Cosa dovrebbe fare il compilatore se si estende da Thread e un'altra classe con il metodo pubblico checkAccess ()? Preferiresti davvero usare puntatori di base con un nome forte come in C ++? Sembra un cattivo design, di solito hai bisogno di una composizione dove pensi di aver bisogno di ereditarietà multipla.
Ha consegnato il

4
@ Ha11owed è stato tanto tempo fa, quindi non ricordo i dettagli, ma aveva metodi e contants che volevo avere nella mia classe e, soprattutto, volevo che il mio oggetto derivato della classe fosse Threadun'istanza. L'eredità multipla può essere una cattiva progettazione e composizione. Tutto dipende dal caso.
doc

2
@Dave: Davvero? Objective-C ha una valutazione in fase di compilazione e modelli?
Deduplicatore

51

Non esiste un concetto di "interfaccia" in sé in C ++. AFAIK, le interfacce sono state introdotte per la prima volta in Java per ovviare alla mancanza di eredità multipla. Questo concetto si è rivelato abbastanza utile e lo stesso effetto può essere ottenuto in C ++ usando una classe base astratta.

Una classe base astratta è una classe in cui almeno una funzione membro (metodo nel linguaggio Java) è una funzione virtuale pura dichiarata utilizzando la sintassi seguente:

class A
{
  virtual void foo() = 0;
};

Non è possibile creare un'istanza di una classe base astratta, ovvero non è possibile dichiarare un oggetto di classe A. È possibile derivare solo le classi da A, ma anche qualsiasi classe derivata che non fornisce un'implementazione foo()sarà astratta. Per smettere di essere astratto, una classe derivata deve fornire implementazioni per tutte le funzioni virtuali pure che eredita.

Si noti che una classe di base astratta può essere più di un'interfaccia, poiché può contenere membri di dati e funzioni membro che non sono pure virtuali. Un equivalente di un'interfaccia sarebbe una classe base astratta senza dati con solo funzioni virtuali pure.

E, come ha sottolineato Mark Ransom, una classe base astratta dovrebbe fornire un distruttore virtuale, proprio come qualsiasi classe base, per quella materia.


13
Più che "mancanza di eredità multipla", direi, per sostituire l'eredità multipla. Java è stato progettato in questo modo sin dall'inizio perché l'ereditarietà multipla crea più problemi di quanti ne risolva. Buona risposta
Oscar Ryz,

11
Oscar, dipende dal fatto che sei un programmatore C ++ che ha imparato Java o viceversa. :) IMHO, se usato con giudizio, come quasi tutto in C ++, l'ereditarietà multipla risolve i problemi. Una classe base astratta "interfaccia" è un esempio di un uso molto giudizioso dell'ereditarietà multipla.
Dima,

8
@OscarRyz Wrong. MI crea problemi solo se usato in modo improprio. La maggior parte dei presunti problemi con l'MI verrebbe anche con progetti alternativi (senza IM). Quando le persone hanno un problema con il loro design con MI, è colpa di MI; se hanno un problema di progettazione con SI, è colpa loro. "Diamante della morte" (eredità ripetuta) è un ottimo esempio. Il colpo di grazia non è pura ipocrisia, ma vicino.
curioso

4
Semanticamente, le interfacce sono diverse dalle classi astratte, quindi le interfacce di Java non sono solo una soluzione tecnica. La scelta tra definire un'interfaccia o una classe astratta è guidata dalla semantica, non da considerazioni tecniche. Immaginiamo un'interfaccia "HasEngine": questo è un aspetto, una caratteristica, e può essere applicato / implementato da tipi molto diversi (che siano classi o classi astratte), quindi definiremo un'interfaccia per quello, non una classe astratta.
Marek Stanley,

2
@MarekStanley, potresti avere ragione, ma vorrei che avessi scelto un esempio migliore. Mi piace pensarci in termini di ereditare un'interfaccia piuttosto che ereditare un'implementazione. In C ++ puoi ereditare insieme sia l'interfaccia che l'implementazione (eredità pubblica) oppure puoi ereditare solo l'implementazione (eredità privata). In Java hai la possibilità di ereditare solo l'interfaccia, senza un'implementazione.
Dima,

43

Per quanto ho potuto testare, è molto importante aggiungere il distruttore virtuale. Sto usando oggetti creati con newe distrutti con delete.

Se non si aggiunge il distruttore virtuale nell'interfaccia, il distruttore della classe ereditata non viene chiamato.

class IBase {
public:
    virtual ~IBase() {}; // destructor, use it to call destructor of the inherit classes
    virtual void Describe() = 0; // pure virtual method
};

class Tester : public IBase {
public:
    Tester(std::string name);
    virtual ~Tester();
    virtual void Describe();
private:
    std::string privatename;
};

Tester::Tester(std::string name) {
    std::cout << "Tester constructor" << std::endl;
    this->privatename = name;
}

Tester::~Tester() {
    std::cout << "Tester destructor" << std::endl;
}

void Tester::Describe() {
    std::cout << "I'm Tester [" << this->privatename << "]" << std::endl;
}


void descriptor(IBase * obj) {
    obj->Describe();
}

int main(int argc, char** argv) {

    std::cout << std::endl << "Tester Testing..." << std::endl;
    Tester * obj1 = new Tester("Declared with Tester");
    descriptor(obj1);
    delete obj1;

    std::cout << std::endl << "IBase Testing..." << std::endl;
    IBase * obj2 = new Tester("Declared with IBase");
    descriptor(obj2);
    delete obj2;

    // this is a bad usage of the object since it is created with "new" but there are no "delete"
    std::cout << std::endl << "Tester not defined..." << std::endl;
    descriptor(new Tester("Not defined"));


    return 0;
}

Se esegui il codice precedente senza virtual ~IBase() {};, vedrai che il distruttore Tester::~Tester()non viene mai chiamato.


3
La migliore risposta in questa pagina in quanto fornisce un esempio pratico e compilabile. Saluti!
Lumi

1
Testet :: ~ Tester () viene eseguito solo quando l'oggetto è "Dichiarato con Tester".
Alessandro L.

In realtà, verrà chiamato il distruttore del stringa privatename e, in memoria, questo è tutto ciò per cui verrà allocato. Per quanto riguarda il runtime, quando tutti i membri concreti di una classe vengono distrutti, lo è anche l'istanza di classe. Ho provato un esperimento simile con una classe Line che aveva due strutture Point e ho scoperto che entrambe le strutture sono state distrutte (Ha!) Su una chiamata di eliminazione o di ritorno dalla funzione comprensiva. valgrind ha confermato 0 perdite.
Chris Reid,

33

La mia risposta è sostanzialmente la stessa delle altre ma penso che ci siano altre due cose importanti da fare:

  1. Dichiara un distruttore virtuale nella tua interfaccia o creane uno non virtuale protetto per evitare comportamenti indefiniti se qualcuno tenta di eliminare un oggetto di tipo IDemo.

  2. Utilizzare l'ereditarietà virtuale per evitare problemi con l'ereditarietà multipla. (Esistono più spesso ereditarietà multiple quando utilizziamo le interfacce.)

E come altre risposte:

  • Crea una classe con metodi virtuali puri.
  • Utilizzare l'interfaccia creando un'altra classe che sovrascrive quei metodi virtuali.

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
            virtual ~IDemo() {}
    }

    O

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
        protected:
            ~IDemo() {}
    }

    E

    class Child : virtual public IDemo
    {
        public:
            virtual void OverrideMe()
            {
                //do stuff
            }
    }

2
non è necessaria l'ereditarietà virtuale poiché non sono presenti membri di dati in un'interfaccia.
Robocide,

3
L'eredità virtuale è importante anche per i metodi. Senza di esso, incontrerai le ambiguità con OverrideMe (), anche se una delle "istanze" è pura virtualità (ho appena provato questo da solo).
Knarf Navillus,

5
@Avishay_ " non è necessaria ereditarietà virtuale in quanto non si hanno membri di dati in un'interfaccia. " Sbagliato.
curioso

Si noti che l'ereditarietà virtuale potrebbe non funzionare su alcune versioni di gcc, come la versione 4.3.3 fornita con WinAVR 2010: gcc.gnu.org/bugzilla/show_bug.cgi?id=35067
mMontu

-1 per avere un distruttore protetto non virtuale, scusa
Wolf

10

In C ++ 11 puoi facilmente evitare del tutto l'ereditarietà:

struct Interface {
  explicit Interface(SomeType& other)
  : foo([=](){ return other.my_foo(); }), 
    bar([=](){ return other.my_bar(); }), /*...*/ {}
  explicit Interface(SomeOtherType& other)
  : foo([=](){ return other.some_foo(); }), 
    bar([=](){ return other.some_bar(); }), /*...*/ {}
  // you can add more types here...

  // or use a generic constructor:
  template<class T>
  explicit Interface(T& other)
  : foo([=](){ return other.foo(); }), 
    bar([=](){ return other.bar(); }), /*...*/ {}

  const std::function<void(std::string)> foo;
  const std::function<void(std::string)> bar;
  // ...
};

In questo caso, un'interfaccia ha una semantica di riferimento, ovvero è necessario assicurarsi che l'oggetto sopravviva all'interfaccia (è anche possibile creare interfacce con la semantica del valore).

Questo tipo di interfacce ha i suoi pro e contro:

Infine, l'eredità è la radice di tutti i mali nella progettazione di software complessi. Nel polimorfismo basato sulla semantica e sui concetti basato sul valore di Sean Parent (qui sono spiegate altamente consigliate, versioni migliori di questa tecnica) viene studiato il seguente caso:

Supponiamo di avere un'applicazione in cui gestisco polimorficamente le mie forme usando l' MyShapeinterfaccia:

struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle

Nella tua applicazione, fai lo stesso con forme diverse usando l' YourShapeinterfaccia:

struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...

Ora dì che vuoi usare alcune delle forme che ho sviluppato nella tua applicazione. Concettualmente, le nostre forme hanno la stessa interfaccia, ma per far funzionare le mie forme nella tua applicazione dovrai estenderle come segue:

struct Circle : MyShape, YourShape { 
  void my_draw() { /*stays the same*/ };
  void your_draw() { my_draw(); }
};

Innanzitutto, modificare le mie forme potrebbe non essere affatto possibile. Inoltre, l'eredità multipla conduce la strada verso il codice spaghetti (immagina che arrivi un terzo progetto che utilizza l' TheirShapeinterfaccia ... cosa succede se chiamano anche la loro funzione di disegno my_draw?).

Aggiornamento: ci sono un paio di nuovi riferimenti sul polimorfismo basato sull'ereditarietà:


5
L'eredità TBH è molto più chiara di quella cosa C ++ 11, che finge di essere un'interfaccia, ma è piuttosto una colla per legare alcuni progetti incoerenti. L'esempio delle forme è distaccato dalla realtà e la Circleclasse ha un design scadente. In Adapterquesti casi è necessario utilizzare il modello. Scusate se sembrerà un po 'duro, ma provate a usare qualche libreria di vita reale come Qtprima di dare giudizi sull'eredità. L'ereditarietà rende la vita molto più semplice.
doc,

2
Non sembra affatto duro. In che modo l'esempio di forma è staccato dalla realtà? Potresti dare un esempio (forse su ideone) di riparare Circle usando il Adaptermodello? Sono interessato a vedere i suoi vantaggi.
gnzlbg,

OK proverò ad inserirmi in questa piccola scatola. Prima di tutto, di solito scegli librerie come "MyShape" prima di iniziare a scrivere la tua applicazione, per proteggere il tuo lavoro. Altrimenti come fai a sapere che Squarenon c'è già? Preconoscenza? Ecco perché è distaccato dalla realtà. E in realtà se scegli di fare affidamento sulla libreria "MyShape" puoi adottare la sua interfaccia fin dall'inizio. Nell'esempio di forme ci sono molte assurdità (una delle quali è che hai due Circlestrutture), ma l'adattatore sarebbe simile a quello -> ideone.com/UogjWk
doc

2
Allora non è distaccato dalla realtà. Quando la società A acquista la società B e desidera integrare la base di codice della società B in A, hai due basi di codice completamente indipendenti. Immagina che ognuno abbia una gerarchia Shape di diversi tipi. Non puoi combinarli facilmente con l'eredità e aggiungere la società C e hai un casino enorme. Penso che dovresti guardare questo discorso: youtube.com/watch?v=0I0FD3N5cgM La mia risposta è più vecchia, ma vedrai le somiglianze. Non è necessario reimplementare tutto in ogni momento, è possibile fornire un'implementazione nell'interfaccia e scegliere una funzione membro se disponibile.
gnzlbg,

1
Ho visto parte del video e questo è totalmente sbagliato. Non uso mai dynamic_cast se non per scopi di debug. Cast dinamico significa che c'è qualcosa di sbagliato nel tuo design e i design in questo video sono sbagliati dal design :). Guy menziona anche Qt, ma anche qui ha torto: QLayout non eredita da QWidget né viceversa!
doc

9

Tutte le buone risposte sopra. Una cosa in più da tenere a mente: puoi anche avere un puro distruttore virtuale. L'unica differenza è che devi ancora implementarlo.

Confuso?


    --- header file ----
    class foo {
    public:
      foo() {;}
      virtual ~foo() = 0;

      virtual bool overrideMe() {return false;}
    };

    ---- source ----
    foo::~foo()
    {
    }

Il motivo principale per cui vorresti fare questo è se vuoi fornire metodi di interfaccia, come ho fatto io, ma renderli facoltativi.

Per rendere la classe una classe di interfaccia richiede un metodo virtuale puro, ma tutti i metodi virtuali hanno implementazioni predefinite, quindi l'unico metodo rimasto per rendere virtuale puro è il distruttore.

Reimplementare un distruttore nella classe derivata non è affatto un grosso problema - ho sempre reimplementare un distruttore, virtuale o no, nelle mie classi derivate.


4
Perché, oh perché, qualcuno vorrebbe rendere il dtor in questo caso puro virtuale? Quale sarebbe il guadagno di quello? Forzeresti qualcosa sulle classi derivate che probabilmente non hanno bisogno di includere: un fattore.
Johann Gerell,

6
Aggiornato la mia risposta per rispondere alla tua domanda. Il puro distruttore virtuale è un modo valido per ottenere (l'unico modo per ottenere?) Una classe di interfaccia in cui tutti i metodi hanno implementazioni predefinite.
Rodyland,

7

Se si utilizza il compilatore C ++ di Microsoft, è possibile effettuare le seguenti operazioni:

struct __declspec(novtable) IFoo
{
    virtual void Bar() = 0;
};

class Child : public IFoo
{
public:
    virtual void Bar() override { /* Do Something */ }
}

Mi piace questo approccio perché si traduce in un codice di interfaccia molto più piccolo e la dimensione del codice generato può essere significativamente più piccola. L'uso di novtable rimuove tutti i riferimenti al puntatore vtable in quella classe, quindi non puoi mai istanziarlo direttamente. Vedi la documentazione qui - novtable .


4
Non capisco bene perché tu abbia usato lo novtablestandardvirtual void Bar() = 0;
Flexo

2
È in aggiunta a (ho appena notato la mancanza = 0;che ho aggiunto). Leggi la documentazione se non la capisci.
Mark Ingram,

L'ho letto senza = 0;e ho pensato che fosse solo un modo non standard di fare esattamente lo stesso.
Flexo

4

Una piccola aggiunta a ciò che è scritto lì:

Innanzitutto, assicurati che anche il tuo distruttore sia puro virtuale

In secondo luogo, potresti voler ereditare virtualmente (piuttosto che normalmente) quando lo fai, solo per buone misure.


Mi piace l'eredità virtuale perché concettualmente significa che esiste solo un'istanza della classe ereditata. Certo, la classe qui non ha alcuno spazio necessario, quindi potrebbe essere superflua. Non ho fatto l'MI in C ++ per un po ', ma l'eredità non virtuale complicherebbe l'upgrade?
Uri,

Perché, oh perché, qualcuno vorrebbe rendere il dtor in questo caso puro virtuale? Quale sarebbe il guadagno di quello? Forzeresti qualcosa sulle classi derivate che probabilmente non hanno bisogno di includere: un fattore.
Johann Gerell,

2
Se c'è una situazione in cui un oggetto verrebbe distrutto attraverso un puntatore all'interfaccia, dovresti assicurarti che il distruttore sia virtuale ...
Uri

Non c'è niente di sbagliato in un puro distruttore virtuale. Non è necessario, ma non c'è niente di sbagliato in questo. L'implementazione di un distruttore in una classe derivata non è certo un onere enorme per l'implementatore di quella classe. Vedi la mia risposta qui sotto per il motivo per cui dovresti farlo.
Rodyland,

+1 per l'eredità virtuale, perché con le interfacce è più probabile che la classe derivi l'interfaccia da due o più percorsi. Opto per distruttori protetti nelle interfacce.
doc

4

Puoi anche prendere in considerazione le classi di contratto implementate con la NVI (Non Virtual Interface Pattern). Per esempio:

struct Contract1 : boost::noncopyable
{
    virtual ~Contract1();
    void f(Parameters p) {
        assert(checkFPreconditions(p)&&"Contract1::f, pre-condition failure");
        // + class invariants.
        do_f(p);
        // Check post-conditions + class invariants.
    }
private:
    virtual void do_f(Parameters p) = 0;
};
...
class Concrete : public Contract1, public Contract2
{
private:
    virtual void do_f(Parameters p); // From contract 1.
    virtual void do_g(Parameters p); // From contract 2.
};

Per altri lettori, questo articolo del Dr Dobbs "Conversations: Virtually Yours" di Jim Hyslop e Herb Sutter elabora un po 'di più il motivo per cui si potrebbe voler usare la NVI.
user2067021

E anche questo articolo "Virtuality" di Herb Sutter.
user2067021

1

Sono ancora nuovo nello sviluppo di C ++. Ho iniziato con Visual Studio (VS).

Eppure, nessuno sembra aver menzionato __interfacein VS (.NET) . Sono Non molto sicuro se questo è un buon modo per dichiarare un'interfaccia. Ma sembra fornire un'ulteriore applicazione (menzionata nei documenti ). Tale che non è necessario specificare esplicitamente il virtual TYPE Method() = 0;, poiché verrà convertito automaticamente.

__interface IMyInterface {
   HRESULT CommitX();
   HRESULT get_X(BSTR* pbstrName);
};

Tuttavia, non lo uso perché sono preoccupato per la compatibilità della compilazione multipiattaforma, poiché è disponibile solo in .NET.

Se qualcuno ha qualcosa di interessante, per favore condividi. :-)

Grazie.


0

Mentre è vero che virtualè lo standard di fatto per definire un'interfaccia, non dimentichiamoci del classico modello in stile C, che viene fornito con un costruttore in C ++:

struct IButton
{
    void (*click)(); // might be std::function(void()) if you prefer

    IButton( void (*click_)() )
    : click(click_)
    {
    }
};

// call as:
// (button.*click)();

Ciò ha il vantaggio di poter ricollegare il runtime degli eventi senza dover ricostruire nuovamente la classe (poiché C ++ non ha una sintassi per cambiare i tipi polimorfici, questa è una soluzione alternativa per le classi camaleonte).

Suggerimenti:

  • Puoi ereditare da questo come una classe di base (sono consentiti sia virtuali che non virtuali) e compilare clickil costruttore del tuo discendente.
  • Potresti avere il puntatore a funzione come protectedmembro e avere un publicriferimento e / o un getter.
  • Come accennato in precedenza, ciò consente di cambiare l'implementazione in fase di esecuzione. Quindi è anche un modo per gestire lo stato. A seconda del numero di ifs vs cambiamenti di stato nel tuo codice, questo potrebbe essere più veloce di switch()es o ifs (l'inversione di tendenza è prevista intorno a 3-4 ifs, ma misura sempre prima.
  • Se scegli i std::function<>puntatori a funzione, potresti essere in grado di gestire tutti i dati degli oggetti all'interno IBase. Da questo punto, puoi avere schemi di valore per IBase(ad esempio, std::vector<IBase>funzionerà). Si noti che questo potrebbe essere più lento a seconda del compilatore e del codice STL; anche che le attuali implementazioni di std::function<>tendono ad avere un sovraccarico rispetto ai puntatori di funzione o anche alle funzioni virtuali (questo potrebbe cambiare in futuro).

0

Ecco la definizione di abstract classstandard in c ++

n4687

13.4.2

Una classe astratta è una classe che può essere utilizzata solo come classe base di un'altra classe; non è possibile creare oggetti di una classe astratta se non come oggetti secondari di una classe derivata da essa. Una classe è astratta se ha almeno una funzione virtuale pura.


-2
class Shape 
{
public:
   // pure virtual function providing interface framework.
   virtual int getArea() = 0;
   void setWidth(int w)
   {
      width = w;
   }
   void setHeight(int h)
   {
      height = h;
   }
protected:
    int width;
    int height;
};

class Rectangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height); 
    }
};
class Triangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height)/2; 
    }
};

int main(void)
{
     Rectangle Rect;
     Triangle  Tri;

     Rect.setWidth(5);
     Rect.setHeight(7);

     cout << "Rectangle area: " << Rect.getArea() << endl;

     Tri.setWidth(5);
     Tri.setHeight(7);

     cout << "Triangle area: " << Tri.getArea() << endl; 

     return 0;
}

Risultato: area rettangolare: 35 Area triangolo: 17

Abbiamo visto come una classe astratta ha definito un'interfaccia in termini di getArea () e altre due classi hanno implementato la stessa funzione ma con algoritmo diverso per calcolare l'area specifica della forma.


5
Questo non è ciò che è considerato un'interfaccia! Questa è solo una classe base astratta con un metodo che deve essere ignorato! Le interfacce sono in genere oggetti che contengono solo definizioni di metodi: un "contratto" che altre classi devono soddisfare quando implementano l'interfaccia.
Guitarflow
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.