Comment configurer une classe qui représente une interface? Est-ce juste une classe de base abstraite?
Comment configurer une classe qui représente une interface? Est-ce juste une classe de base abstraite?
Réponses:
Pour développer la réponse de bradtgmurray , vous souhaiterez peut-être faire une exception à la liste des méthodes virtuelles pures de votre interface en ajoutant un destructeur virtuel. Cela vous permet de transmettre la propriété du pointeur à une autre partie sans exposer la classe dérivée concrète. Le destructeur n'a rien à faire, car l'interface n'a pas de membres concrets. Il peut sembler contradictoire de définir une fonction à la fois virtuelle et en ligne, mais croyez-moi, ce n'est pas le cas.
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
}
};
Vous n'avez pas besoin d'inclure un corps pour le destructeur virtuel - il s'avère que certains compilateurs ont du mal à optimiser un destructeur vide et il vaut mieux utiliser la valeur par défaut.
=0
destructeur virtuel ( ) pur avec un corps. L'avantage ici est que le compilateur peut, théoriquement, voir que vtable n'a plus de membres valides maintenant, et le supprimer complètement. Avec un destructeur virtuel avec un corps, ledit destructeur peut être appelé (virtuellement) par exemple au milieu de la construction via un this
pointeur (lorsque l'objet construit est toujours de Parent
type), et donc le compilateur doit fournir une vtable valide. Donc, si vous n'appelez pas explicitement les destructeurs virtuels via this
pendant la construction :), vous pouvez économiser sur la taille du code.
override
mot clé pour permettre l'argument de compilation et la vérification du type de valeur de retour. Par exemple, dans la déclaration de Childvirtual void OverrideMe() override;
Créez une classe avec des méthodes virtuelles pures. Utilisez l'interface en créant une autre classe qui remplace ces méthodes virtuelles.
Une méthode virtuelle pure est une méthode de classe définie comme virtuelle et affectée à 0.
class IDemo
{
public:
virtual ~IDemo() {}
virtual void OverrideMe() = 0;
};
class Child : public IDemo
{
public:
virtual void OverrideMe()
{
//do stuff
}
};
override
en C ++ 11
La raison pour laquelle vous avez une catégorie de type Interface spéciale en plus des classes de base abstraites en C # / Java est parce que C # / Java ne prend pas en charge l'héritage multiple.
C ++ prend en charge l'héritage multiple, et donc un type spécial n'est pas nécessaire. Une classe de base abstraite sans méthode non abstraite (virtuelle pure) est fonctionnellement équivalente à une interface C # / Java.
Thread
instance. L'héritage multiple peut être une mauvaise conception ainsi qu'une mauvaise composition. Tout dépend du cas.
Il n'y a pas de concept d '"interface" en soi en C ++. AFAIK, les interfaces ont d'abord été introduites en Java pour contourner le manque d'héritage multiple. Ce concept s'est avéré très utile et le même effet peut être obtenu en C ++ en utilisant une classe de base abstraite.
Une classe de base abstraite est une classe dans laquelle au moins une fonction membre (méthode en langage Java) est une fonction virtuelle pure déclarée à l'aide de la syntaxe suivante:
class A
{
virtual void foo() = 0;
};
Une classe de base abstraite ne peut pas être instanciée, c'est-à-dire que vous ne pouvez pas déclarer un objet de classe A. Vous pouvez uniquement dériver des classes de A, mais toute classe dérivée qui ne fournit pas d'implémentation de foo()
sera également abstraite. Pour ne plus être abstraite, une classe dérivée doit fournir des implémentations pour toutes les fonctions virtuelles pures dont elle hérite.
Notez qu'une classe de base abstraite peut être plus qu'une interface, car elle peut contenir des membres de données et des fonctions membres qui ne sont pas purement virtuels. Un équivalent d'une interface serait une classe de base abstraite sans aucune donnée avec uniquement des fonctions virtuelles pures.
Et, comme l'a souligné Mark Ransom, une classe de base abstraite devrait fournir un destructeur virtuel, comme toute classe de base, d'ailleurs.
Autant que j'ai pu tester, il est très important d'ajouter le destructeur virtuel. J'utilise des objets créés avec new
et détruits avec delete
.
Si vous n'ajoutez pas le destructeur virtuel dans l'interface, le destructeur de la classe héritée n'est pas appelé.
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;
}
Si vous exécutez le code précédent sans virtual ~IBase() {};
, vous verrez que le destructeur Tester::~Tester()
n'est jamais appelé.
Ma réponse est essentiellement la même que les autres, mais je pense qu'il y a deux autres choses importantes à faire:
Déclarez un destructeur virtuel dans votre interface ou créez-en un non virtuel pour éviter les comportements non définis si quelqu'un essaie de supprimer un objet de type IDemo
.
Utilisez l'héritage virtuel pour éviter les problèmes d'héritage multiple. (Il y a plus souvent un héritage multiple lorsque nous utilisons des interfaces.)
Et comme les autres réponses:
Utilisez l'interface en créant une autre classe qui remplace ces méthodes virtuelles.
class IDemo
{
public:
virtual void OverrideMe() = 0;
virtual ~IDemo() {}
}
Ou
class IDemo
{
public:
virtual void OverrideMe() = 0;
protected:
~IDemo() {}
}
Et
class Child : virtual public IDemo
{
public:
virtual void OverrideMe()
{
//do stuff
}
}
Dans C ++ 11, vous pouvez facilement éviter complètement l'héritage:
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;
// ...
};
Dans ce cas, une interface a une sémantique de référence, c'est-à-dire que vous devez vous assurer que l'objet survit à l'interface (il est également possible de faire des interfaces avec une sémantique de valeur).
Ces types d'interfaces ont leurs avantages et leurs inconvénients:
Enfin, l'héritage est la racine de tout mal dans la conception de logiciels complexes. Dans Sean Parent's Value Semantics and Concepts-based Polymorphism (fortement recommandé, de meilleures versions de cette technique y sont expliquées), le cas suivant est étudié:
Disons que j'ai une application dans laquelle je traite mes formes de manière polymorphe en utilisant l' MyShape
interface:
struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle
Dans votre application, vous faites de même avec différentes formes en utilisant l' YourShape
interface:
struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...
Supposons maintenant que vous souhaitiez utiliser certaines des formes que j'ai développées dans votre application. Conceptuellement, nos formes ont la même interface, mais pour que mes formes fonctionnent dans votre application, vous devez étendre mes formes comme suit:
struct Circle : MyShape, YourShape {
void my_draw() { /*stays the same*/ };
void your_draw() { my_draw(); }
};
Tout d'abord, la modification de mes formes pourrait ne pas être possible du tout. De plus, l'héritage multiple ouvre la voie au code spaghetti (imaginez qu'un troisième projet arrive qui utilise l' TheirShape
interface ... que se passe-t-il s'ils appellent également leur fonction de dessin my_draw
?).
Mise à jour: Il existe quelques nouvelles références sur le polymorphisme non hérité:
Circle
classe est une mauvaise conception. Vous devez utiliser le Adapter
modèle dans de tels cas. Désolé si cela peut sembler un peu dur, mais essayez d'utiliser une bibliothèque réelle comme Qt
avant de porter un jugement sur l'héritage. L'héritage rend la vie beaucoup plus facile.
Adapter
modèle? Je suis intéressé de voir ses avantages.
Square
n'est pas déjà là? Connaissance anticipée? C'est pourquoi il est détaché de la réalité. Et en réalité, si vous choisissez de vous appuyer sur la bibliothèque "MyShape", vous pouvez l'adopter dès le début. Dans l'exemple de formes, il y a beaucoup de non-sens (dont l'un est que vous avez deux Circle
structures), mais l'adaptateur ressemblerait à quelque chose comme ça -> ideone.com/UogjWk
Toutes les bonnes réponses ci-dessus. Une chose supplémentaire que vous devez garder à l'esprit - vous pouvez également avoir un destructeur virtuel pur. La seule différence est que vous devez toujours l'implémenter.
Confus?
--- header file ----
class foo {
public:
foo() {;}
virtual ~foo() = 0;
virtual bool overrideMe() {return false;}
};
---- source ----
foo::~foo()
{
}
La principale raison pour laquelle vous voudriez le faire est que si vous souhaitez fournir des méthodes d'interface, comme je l'ai fait, mais que vous les rendiez facultatives.
Pour faire de la classe une classe d'interface, il faut une méthode virtuelle pure, mais toutes vos méthodes virtuelles ont des implémentations par défaut, donc la seule méthode qui reste pour faire du virtuel pur est le destructeur.
Réimplémenter un destructeur dans la classe dérivée n'est pas un problème du tout - je réimplémente toujours un destructeur, virtuel ou non, dans mes classes dérivées.
Si vous utilisez le compilateur C ++ de Microsoft, vous pouvez effectuer les opérations suivantes:
struct __declspec(novtable) IFoo
{
virtual void Bar() = 0;
};
class Child : public IFoo
{
public:
virtual void Bar() override { /* Do Something */ }
}
J'aime cette approche car elle se traduit par un code d'interface beaucoup plus petit et la taille du code généré peut être considérablement plus petite. L'utilisation de novtable supprime toute référence au pointeur vtable dans cette classe, vous ne pouvez donc jamais l'instancier directement. Voir la documentation ici - novtable .
novtable
plus que la normevirtual void Bar() = 0;
= 0;
que j'ai ajouté). Lisez la documentation si vous ne la comprenez pas.
= 0;
et j'ai supposé que c'était juste une façon non standard de faire exactement la même chose.
Un petit ajout à ce qui est écrit là-bas:
Tout d'abord, assurez-vous que votre destructeur est également purement virtuel
Deuxièmement, vous souhaiterez peut-être hériter virtuellement (plutôt que normalement) lorsque vous l'implémenterez, juste pour de bonnes mesures.
Vous pouvez également envisager des classes de contrat implémentées avec le NVI (Non Virtual Interface Pattern). Par exemple:
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.
};
Je suis encore nouveau dans le développement C ++. J'ai commencé avec Visual Studio (VS).
Pourtant, personne ne semble avoir mentionné le __interface
dans VS (.NET) . Je ne suis pas très sûr que ce soit un bon moyen de déclarer une interface. Mais il semble fournir une application supplémentaire (mentionnée dans les documents ). Telle que vous n'avez pas à spécifier explicitement le virtual TYPE Method() = 0;
, car il sera automatiquement converti.
__interface IMyInterface {
HRESULT CommitX();
HRESULT get_X(BSTR* pbstrName);
};
Cependant, je ne l'utilise pas parce que je m'inquiète de la compatibilité de compilation multiplateforme, car elle n'est disponible que sous .NET.
Si quelqu'un a quelque chose d'intéressant à ce sujet, veuillez le partager. :-)
Merci.
S'il est vrai que virtual
c'est la norme de facto pour définir une interface, n'oublions pas le modèle classique de type C, qui vient avec un constructeur en C ++:
struct IButton
{
void (*click)(); // might be std::function(void()) if you prefer
IButton( void (*click_)() )
: click(click_)
{
}
};
// call as:
// (button.*click)();
Cela a l'avantage que vous pouvez lier à nouveau le runtime des événements sans avoir à reconstruire votre classe (car C ++ n'a pas de syntaxe pour changer les types polymorphes, c'est une solution de contournement pour les classes caméléons).
Conseils:
click
le constructeur de votre descendant.protected
membre et avoir une public
référence et / ou un getter.if
changements de s par rapport à l'état dans votre code, cela peut être plus rapide que switch()
es ou if
s (un délai est prévu autour de 3-4 if
s, mais mesurez toujours en premier.std::function<>
des pointeurs de fonction, vous pourrez peut- être gérer toutes vos données d'objet à l'intérieur IBase
. À partir de ce point, vous pouvez avoir des schémas de valeur IBase
(par exemple, std::vector<IBase>
cela fonctionnera). Notez que cela peut être plus lent selon votre compilateur et votre code STL; également que les implémentations actuelles de std::function<>
ont tendance à avoir un surcoût par rapport aux pointeurs de fonction ou même aux fonctions virtuelles (cela pourrait changer à l'avenir).Voici la définition de la abstract class
norme c ++
n4687
13.4.2
Une classe abstraite est une classe qui ne peut être utilisée que comme classe de base d'une autre classe; aucun objet d'une classe abstraite ne peut être créé sauf en tant que sous-objets d'une classe qui en dérive. Une classe est abstraite si elle a au moins une fonction virtuelle pure.
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;
}
Résultat: Rectangle: 35 Triangle: 17
Nous avons vu comment une classe abstraite a défini une interface en termes de getArea () et deux autres classes ont implémenté la même fonction mais avec un algorithme différent pour calculer la zone spécifique à la forme.