Come posso impostare una classe che rappresenta un'interfaccia? È solo una classe base astratta?
Come posso impostare una classe che rappresenta un'interfaccia? È solo una classe base astratta?
Risposte:
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.
=0
distruttore 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 this
puntatore (quando l'oggetto costruito è ancora di Parent
tipo), e quindi il compilatore deve fornire una vtable valida. Quindi se non chiami esplicitamente i distruttori virtuali this
durante la costruzione :) puoi risparmiare sulla dimensione del codice.
override
parola chiave per consentire l'argomento di compilazione e il controllo del tipo di valore restituito. Ad esempio, nella dichiarazione di Childvirtual void OverrideMe() override;
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
}
};
override
in C ++ 11
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.
Thread
un'istanza. L'eredità multipla può essere una cattiva progettazione e composizione. Tutto dipende dal caso.
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.
Per quanto ho potuto testare, è molto importante aggiungere il distruttore virtuale. Sto usando oggetti creati con new
e 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.
La mia risposta è sostanzialmente la stessa delle altre ma penso che ci siano altre due cose importanti da fare:
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
.
Utilizzare l'ereditarietà virtuale per evitare problemi con l'ereditarietà multipla. (Esistono più spesso ereditarietà multiple quando utilizziamo le interfacce.)
E come altre risposte:
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
}
}
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' MyShape
interfaccia:
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' YourShape
interfaccia:
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' TheirShape
interfaccia ... 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à:
Circle
classe ha un design scadente. In Adapter
questi casi è necessario utilizzare il modello. Scusate se sembrerà un po 'duro, ma provate a usare qualche libreria di vita reale come Qt
prima di dare giudizi sull'eredità. L'ereditarietà rende la vita molto più semplice.
Adapter
modello? Sono interessato a vedere i suoi vantaggi.
Square
non 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 Circle
strutture), ma l'adattatore sarebbe simile a quello -> ideone.com/UogjWk
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.
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 .
novtable
standardvirtual void Bar() = 0;
= 0;
che ho aggiunto). Leggi la documentazione se non la capisci.
= 0;
e ho pensato che fosse solo un modo non standard di fare esattamente lo stesso.
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.
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.
};
Sono ancora nuovo nello sviluppo di C ++. Ho iniziato con Visual Studio (VS).
Eppure, nessuno sembra aver menzionato __interface
in 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.
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:
click
il costruttore del tuo discendente.protected
membro e avere un public
riferimento e / o un getter.if
s vs cambiamenti di stato nel tuo codice, questo potrebbe essere più veloce di switch()
es o if
s (l'inversione di tendenza è prevista intorno a 3-4 if
s, ma misura sempre prima.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).Ecco la definizione di abstract class
standard 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.
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.