Comment déclarez-vous une interface en C ++?

Réponses:

686

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.

Mark Ransom
la source
106
Desctuctor virtuel ++! C'est très important. Vous pouvez également inclure des déclarations virtuelles pures de l'opérateur = et copier les définitions de constructeur pour empêcher le compilateur de générer automatiquement celles-ci pour vous.
Xan
33
Une alternative à un destructeur virtuel est un destructeur protégé. Cela désactive la destruction polymorphe, qui peut être plus appropriée dans certaines circonstances. Recherchez «Ligne directrice # 4» dans gotw.ca/publications/mill18.htm .
Fred Larson
9
Une autre option consiste à définir un =0destructeur 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 thispointeur (lorsque l'objet construit est toujours de Parenttype), et donc le compilateur doit fournir une vtable valide. Donc, si vous n'appelez pas explicitement les destructeurs virtuels via thispendant la construction :), vous pouvez économiser sur la taille du code.
Pavel Minaev
51
Combien typique d'une réponse C ++ que la première réponse ne répond pas directement à la question (bien que le code soit évidemment parfait), au lieu de cela, elle optimise la réponse simple.
Tim
18
N'oubliez pas qu'en C ++ 11, vous pouvez spécifier le overridemot 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;
Sean
245

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
        }
};
bradtgmurray
la source
29
vous devriez avoir un destructeur de ne rien faire dans IDemo pour qu'il soit défini un comportement à faire: IDemo * p = new Child; / * n'importe quoi * / supprimer p;
Evan Teran
11
Pourquoi la méthode OverrideMe dans la classe Child est virtuelle? Est-ce que c'est nécessaire?
Cemre
9
@Cemre - non ce n'est pas nécessaire, mais ça ne fait pas de mal non plus.
PowerApp101
11
C'est généralement une bonne idée de conserver le mot-clé «virtuel» chaque fois que vous remplacez une méthode virtuelle. Bien que non obligatoire, il peut rendre le code plus clair - sinon, vous n'avez aucune indication que cette méthode pourrait être utilisée de manière polymorphe, ou même existe dans la classe de base.
Kevin
27
@Kevin Except avec overrideen C ++ 11
keyser
146

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.

Joel Coehoorn
la source
17
Ce serait quand même bien de pouvoir créer des interfaces, pour nous éviter de taper autant (virtuel, = 0, destructeur virtuel). L'héritage multiple me semble également être une très mauvaise idée et je ne l'ai jamais vu utilisé dans la pratique, mais des interfaces sont nécessaires tout le temps. Dommage que le comité C ++ n'introduise pas d'interfaces simplement parce que je les veux.
Ha11owed
9
Ha11owed: Il a des interfaces. On les appelle des classes avec des méthodes virtuelles pures et aucune implémentation de méthode.
Miles Rout
6
@doc: java.lang.Thread a des méthodes et des constantes que vous ne voulez probablement pas avoir dans votre objet. Que doit faire le compilateur si vous étendez à partir de Thread et d'une autre classe avec la méthode publique checkAccess ()? Préféreriez-vous vraiment utiliser des pointeurs de base fortement nommés comme en C ++? Cela ressemble à une mauvaise conception, vous avez généralement besoin d'une composition où vous pensez avoir besoin d'un héritage multiple.
Ha11owed
4
@ Ha11owed c'était il y a longtemps donc je ne me souviens pas des détails, mais il y avait des méthodes et des contants que je voulais avoir dans ma classe et surtout je voulais que mon objet de classe dérivée soit une Threadinstance. L'héritage multiple peut être une mauvaise conception ainsi qu'une mauvaise composition. Tout dépend du cas.
doc
2
@Dave: Vraiment? Objective-C a une évaluation au moment de la compilation et des modèles?
Déduplicateur
51

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.

Dima
la source
13
Plus que «manque d'héritage multiple», je dirais, pour remplacer l'héritage multiple. Java a été conçu comme ça depuis le début car l'héritage multiple crée plus de problèmes que ce qu'il résout. Bonne réponse
OscarRyz
11
Oscar, cela dépend si vous êtes un programmeur C ++ qui a appris Java ou vice versa. :) À mon humble avis, s'il est utilisé judicieusement, comme presque tout en C ++, l'héritage multiple résout les problèmes. Une classe de base abstraite "interface" est un exemple d'utilisation très judicieuse de l'héritage multiple.
Dima
8
@OscarRyz Wrong. L'IM ne crée de problème que lorsqu'il est mal utilisé. La plupart des problèmes allégués avec MI trouveraient également des conceptions alternatives (sans MI). Lorsque les gens ont un problème avec leur conception avec MI, c'est la faute de MI; s'ils ont un problème de conception avec SI, c'est leur faute. "Diamond of death" (héritage répété) en est un excellent exemple. Le dénigrement de l'IM n'est pas une pure hypocrisie, mais proche.
curiousguy
4
Sémantiquement, les interfaces sont différentes des classes abstraites, donc les interfaces de Java ne sont pas seulement une solution de contournement technique. Le choix entre définir une interface ou une classe abstraite est dicté par la sémantique et non par des considérations techniques. Imaginons une interface "HasEngine": c'est un aspect, une fonctionnalité, et elle peut être appliquée à / implémentée par des types très différents (que ce soit des classes ou des classes abstraites), nous allons donc définir une interface pour cela, pas une classe abstraite.
Marek Stanley
2
@MarekStanley, vous avez peut-être raison, mais j'aurais aimé que vous choisissiez un meilleur exemple. J'aime y penser en termes d'héritage d'une interface par rapport à l'héritage d'une implémentation. En C ++, vous pouvez soit hériter à la fois de l'interface et de l'implémentation (héritage public), soit hériter uniquement de l'implémentation (héritage privé). En Java, vous avez la possibilité d'hériter uniquement de l'interface, sans implémentation.
Dima
43

Autant que j'ai pu tester, il est très important d'ajouter le destructeur virtuel. J'utilise des objets créés avec newet 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é.

Carlos C Soto
la source
3
Meilleure réponse sur cette page car elle fait le point en fournissant un exemple pratique et compilable. À votre santé!
Lumi
1
Testet :: ~ Tester () ne s'exécute que lorsque l'obj est "Déclaré avec Tester".
Alessandro L.
En fait, le destructeur du nom de chaîne privé sera appelé, et en mémoire, c'est tout ce qui sera alloué. En ce qui concerne le runtime, lorsque tous les membres concrets d'une classe sont détruits, l'instance de classe l'est également. J'ai essayé une expérience similaire avec une classe Line qui avait deux structures Point et j'ai découvert que les deux structures étaient détruites (Ha!) Lors d'un appel de suppression ou d'un retour de la fonction englobante. valgrind a confirmé 0 fuite.
Chris Reid,
33

Ma réponse est essentiellement la même que les autres, mais je pense qu'il y a deux autres choses importantes à faire:

  1. 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.

  2. 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:

  • 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.

    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
            }
    }
Rexxar
la source
2
il n'y a pas besoin d'héritage virtuel car vous n'avez aucun membre de données dans une interface.
Robocide le
3
L'héritage virtuel est également important pour les méthodes. Sans cela, vous rencontrerez des ambiguïtés avec OverrideMe (), même si l'une de ses `` instances '' est pure virtuelle (venez de l'essayer moi-même).
Knarf Navillus
5
@Avishay_ " il n'y a pas besoin d'héritage virtuel car vous n'avez aucun membre de données dans une interface. " Faux.
curiousguy
Notez que l'héritage virtuel peut ne pas fonctionner sur certaines versions de gcc, comme la version 4.3.3 fournie
mMontu
-1 pour avoir un destructeur protégé non virtuel, désolé
Wolf
10

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' MyShapeinterface:

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' YourShapeinterface:

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' TheirShapeinterface ... 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é:

gnzlbg
la source
5
L'héritage TBH est beaucoup plus clair que cette chose C ++ 11, qui prétend être une interface, mais est plutôt une colle pour lier certaines conceptions incohérentes. L'exemple des formes est détaché de la réalité et la Circleclasse est une mauvaise conception. Vous devez utiliser le Adaptermodèle dans de tels cas. Désolé si cela peut sembler un peu dur, mais essayez d'utiliser une bibliothèque réelle comme Qtavant de porter un jugement sur l'héritage. L'héritage rend la vie beaucoup plus facile.
doc
2
Cela ne semble pas dur du tout. Comment l'exemple de forme est-il détaché de la réalité? Pourriez-vous donner un exemple (peut-être sur une idée) de fixation de Circle en utilisant le Adaptermodèle? Je suis intéressé de voir ses avantages.
gnzlbg
OK je vais essayer de rentrer dans cette petite boite. Tout d'abord, vous choisissez généralement des bibliothèques comme "MyShape" avant de commencer à écrire votre propre application, pour sécuriser votre travail. Sinon, comment pourriez-vous savoir qu'il Squaren'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 Circlestructures), mais l'adaptateur ressemblerait à quelque chose comme ça -> ideone.com/UogjWk
doc
2
Il n'est donc pas détaché de la réalité. Lorsque la société A achète la société B et souhaite intégrer la base de code de la société B dans les A, vous disposez de deux bases de code complètement indépendantes. Imaginez que chacun ait une hiérarchie de formes de différents types. Vous ne pouvez pas les combiner facilement avec l'héritage, et ajoutez la société C et vous avez un énorme gâchis. Je pense que vous devriez regarder cette conférence: youtube.com/watch?v=0I0FD3N5cgM Ma réponse est plus ancienne, mais vous verrez les similitudes. Vous n'avez pas besoin de tout réimplémenter tout le temps, vous pouvez fournir une implémentation dans l'interface et choisir une fonction membre si disponible.
gnzlbg
1
J'ai regardé une partie de la vidéo et c'est totalement faux. Je n'utilise jamais dynamic_cast, sauf à des fins de débogage. La diffusion dynamique signifie qu'il y a un problème avec votre conception et les conceptions de cette vidéo sont incorrectes par conception :). Guy mentionne même Qt, mais même ici, il a tort - QLayout n'hérite pas de QWidget ni l'inverse!
doc
9

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.

Rodyland
la source
4
Pourquoi, oh pourquoi, quelqu'un voudrait-il rendre le dtor dans ce cas pur virtuel? Quel serait le gain de cela? Vous ne feriez qu'imposer quelque chose aux classes dérivées qu'elles n'ont probablement pas besoin d'inclure - un dtor.
Johann Gerell
6
Mis à jour ma réponse pour répondre à votre question. Le destructeur virtuel pur est un moyen valide pour atteindre (le seul moyen d'atteindre?) Une classe d'interface où toutes les méthodes ont des implémentations par défaut.
Rodyland
7

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 .

Mark Ingram
la source
4
Je ne vois pas vraiment pourquoi vous avez utilisé novtableplus que la normevirtual void Bar() = 0;
Flexo
2
C'est en plus de (je viens de remarquer le manquant = 0;que j'ai ajouté). Lisez la documentation si vous ne la comprenez pas.
Mark Ingram
Je l'ai lu sans le = 0;et j'ai supposé que c'était juste une façon non standard de faire exactement la même chose.
Flexo
4

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.

Uri
la source
J'aime l'héritage virtuel car conceptuellement, cela signifie qu'il n'y a qu'une seule instance de la classe héritée. Certes, la classe ici n'a pas d'espace requis et peut donc être superflue. Je n'ai pas fait d'IM en C ++ depuis un moment, mais l'héritage non virtuel ne compliquerait-il pas l'upcasting?
Uri
Pourquoi, oh pourquoi, quelqu'un voudrait-il rendre le dtor dans ce cas pur virtuel? Quel serait le gain de cela? Vous ne feriez qu'imposer quelque chose aux classes dérivées qu'elles n'ont probablement pas besoin d'inclure - un dtor.
Johann Gerell
2
S'il y a une situation où un objet serait détruit via un pointeur vers l'interface, vous devez vous assurer que le destructeur est virtuel ...
Uri
Il n'y a rien de mal avec un destructeur virtuel pur. Ce n'est pas nécessaire, mais il n'y a rien de mal à cela. L'implémentation d'un destructeur dans une classe dérivée n'est guère une lourde charge pour l'implémenteur de cette classe. Voir ma réponse ci-dessous pour savoir pourquoi vous feriez cela.
Rodyland
+1 pour l'héritage virtuel, car avec les interfaces, il est plus probable que la classe dérive l'interface de deux chemins ou plus. J'opte pour des destructeurs protégés dans les interfaces tho.
doc
4

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.
};
Luc Hermitte
la source
Pour les autres lecteurs, cet article du Dr Dobbs "Conversations: Virtually Yours" par Jim Hyslop et Herb Sutter explique un peu plus pourquoi on pourrait vouloir utiliser le NVI.
user2067021
Et aussi cet article "Virtuality" de Herb Sutter.
user2067021
1

Je suis encore nouveau dans le développement C ++. J'ai commencé avec Visual Studio (VS).

Pourtant, personne ne semble avoir mentionné le __interfacedans 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.

Yeo
la source
0

S'il est vrai que virtualc'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:

  • Vous pouvez en hériter en tant que classe de base (à la fois virtuelle et non virtuelle sont autorisées) et remplir clickle constructeur de votre descendant.
  • Vous pouvez avoir le pointeur de fonction en tant que protectedmembre et avoir une publicréférence et / ou un getter.
  • Comme mentionné ci-dessus, cela vous permet de changer d'implémentation lors de l'exécution. C'est donc aussi un moyen de gérer l'État. Selon le nombre de ifchangements de s par rapport à l'état dans votre code, cela peut être plus rapide que switch()es ou ifs (un délai est prévu autour de 3-4 ifs, mais mesurez toujours en premier.
  • Si vous choisissez 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).
lorro
la source
0

Voici la définition de la abstract classnorme 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.

陳 力
la source
-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;
}

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.

lui
la source
5
Ce n'est pas ce qui est considéré comme une interface! C'est juste une classe de base abstraite avec une méthode qui doit être remplacée! Les interfaces sont généralement des objets qui ne contiennent que des définitions de méthode - un «contrat» que les autres classes doivent remplir lorsqu'elles implémentent l'interface.
guitarflow