Pourquoi ne pouvons-nous pas déclarer un std :: vector <AbstractClass>?

88

Ayant passé un certain temps à développer en C #, j'ai remarqué que si vous déclarez une classe abstraite dans le but de l'utiliser comme interface, vous ne pouvez pas instancier un vecteur de cette classe abstraite pour stocker des instances des classes enfants.

#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 ligne déclarant le vecteur de classe abstraite provoque cette erreur dans MS VS2005:

error C2259: 'IFunnyInterface' : cannot instantiate abstract class

Je vois une solution de contournement évidente, qui consiste à remplacer IFunnyInterface par ce qui suit:

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

Est-ce une solution de contournement acceptable en matière de C ++? Sinon, y a-t-il une bibliothèque tierce comme boost qui pourrait m'aider à contourner cela?

Merci d'avoir lu ceci !

Anthony

BlueTrin
la source

Réponses:

127

Vous ne pouvez pas instancier des classes abstraites, donc un vecteur de classes abstraites ne peut pas fonctionner.

Vous pouvez cependant utiliser un vecteur de pointeurs vers des classes abstraites:

std::vector<IFunnyInterface*> ifVec;

Cela vous permet également d'utiliser réellement un comportement polymorphe - même si la classe n'était pas abstraite, le stockage par valeur conduirait au problème du découpage d'objets .

Georg Fritzsche
la source
5
ou vous pouvez utiliser std :: vector <std :: tr1 :: shared_ptr <IFunnyInterface>> si vous ne voulez pas gérer la durée de vie des objets manuellement.
Sergey Teplyakov
4
Ou mieux encore, boost :: ptr_vector <>.
Roel
7
Ou maintenant, std :: vector <std :: unique_ptr <IFunnyInterface>>.
Kaz Dragon
21

Vous ne pouvez pas créer un vecteur d'un type de classe abstraite car vous ne pouvez pas créer d'instances d'une classe abstraite et des conteneurs de bibliothèque standard C ++ comme std :: vector stockent des valeurs (c'est-à-dire des instances). Si vous souhaitez faire cela, vous devrez créer un vecteur de pointeurs vers le type de classe abstraite.

Votre solution de contournement ne fonctionnerait pas car les fonctions virtuelles (c'est pourquoi vous voulez la classe abstraite en premier lieu) ne fonctionnent que lorsqu'elles sont appelées via des pointeurs ou des références. Vous ne pouvez pas non plus créer de vecteurs de références, c'est donc une deuxième raison pour laquelle vous devez utiliser un vecteur de pointeurs.

Vous devez savoir que C ++ et C # ont très peu de points communs. Si vous avez l'intention d'apprendre le C ++, vous devriez le considérer comme partant de zéro et lire un bon didacticiel C ++ dédié tel que Accelerated C ++ de Koenig et Moo.


la source
Merci d'avoir recommandé un livre en plus de répondre au message!
BlueTrin
Mais lorsque vous déclarez un vecteur de classes abstraites, vous ne lui demandez pas de créer une classe abstraite, juste un vecteur capable de contenir une sous-classe non abstraite de cette classe? À moins que vous ne transmettiez un nombre au constructeur de vecteurs, comment peut-il savoir combien d'instances de la classe abstraite créer?
Jonathan.
6

Dans ce cas, nous ne pouvons même pas utiliser ce code:

std::vector <IFunnyInterface*> funnyItems;

ou

std::vector <std::tr1::shared_ptr<IFunnyInterface> > funnyItems;

Parce qu'il n'y a pas de relation IS A entre FunnyImpl et IFunnyInterface et qu'il n'y a pas de conversion implicite entre FUnnyImpl et IFunnyInterface en raison de l'héritage privé.

Vous devez mettre à jour votre code comme suit:

class IFunnyInterface
{
public:
    virtual void IamFunny()  = 0;
};

class FunnyImpl: public IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        cout << "<INSERT JOKE HERE>";
    }
};
Sergey Teplyakov
la source
1
La plupart des gens ont regardé l'héritage privé je pense :) Mais ne confondons pas encore plus l'OP :)
Roel
1
Oui. Surtout après la phrase du sujet de départ: "Ayant passé pas mal de temps à développer en C #" (où aucun héritage privé du tout).
Sergey Teplyakov
6

L'alternative traditionnelle est d'utiliser un vectordes pointeurs, comme déjà noté.

Pour ceux qui apprécient, Boostest livré avec une bibliothèque très intéressante: Pointer Containersqui est parfaitement adaptée à la tâche et vous libère des différents problèmes qu'impliquent les pointeurs:

  • gestion de la vie
  • double déréférencement des itérateurs

Notez que c'est nettement mieux qu'un vectordes pointeurs intelligents, à la fois en termes de performances et d'interface.

Maintenant, il existe une 3ème alternative, qui est de changer votre hiérarchie. Pour une meilleure isolation de l'utilisateur, j'ai vu plusieurs fois le motif suivant utilisé:

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 { .. };

C'est assez simple et une variante de l' Pimplidiome enrichie par un Strategymodèle.

Cela ne fonctionne, bien sûr, que dans le cas où vous ne souhaitez pas manipuler directement les "vrais" objets, et implique une copie profonde. Donc, ce n'est peut-être pas ce que vous souhaitez.

Matthieu M.
la source
1
Merci pour la référence Boost et le modèle de conception
BlueTrin
2

Parce que pour redimensionner un vecteur, vous devez utiliser le constructeur par défaut et la taille de la classe, ce qui à son tour nécessite qu'il soit concret.

Vous pouvez utiliser un pointeur comme autre suggéré.

KennyTM
la source
1

std :: vector essaiera d'allouer de la mémoire pour contenir votre type. Si votre classe est purement virtuelle, le vecteur ne peut pas connaître la taille de la classe qu'il devra allouer.

Je pense qu'avec votre solution de contournement, vous serez en mesure de compiler un vector<IFunnyInterface>mais vous ne pourrez pas manipuler FunnyImpl à l'intérieur. Par exemple, si IFunnyInterface (classe abstraite) est de taille 20 (je ne sais pas vraiment) et FunnyImpl est de taille 30 car il a plus de membres et de code, vous finirez par essayer de mettre 30 dans votre vecteur de 20

La solution serait d'allouer de la mémoire sur le tas avec "nouveau" et de stocker les pointeurs dans vector<IFunnyInterface*>

Eric
la source
Je pensais que c'était la réponse, mais recherchez la réponse gf et le tranchage d'objet, cela explique exactement ce qui se passera dans le conteneur
BlueTrin
Cette réponse décrivait ce qui se passerait mais sans utiliser le mot «tranchage», donc cette réponse est correcte. Lors de l'utilisation d'un vecteur de ptrs, aucun découpage ne se produira. C'est tout l'intérêt d'utiliser ptrs en premier lieu.
Roel le
-2

Je pense que la cause première de cette triste limitation est le fait que les constructeurs ne peuvent pas virtuels. Le compilateur ne peut pas générer de code qui copie l'objet sans connaître son heure au moment de la compilation.

David Gruzman
la source
2
Ce n'est pas la cause profonde et ce n'est pas une "triste limitation".
Veuillez expliquer pourquoi pensez-vous qu'il ne s'agit pas d'une limitation? Ce serait bien d'avoir la capacité. Et il y a une surcharge sur le programmeur quand il / elle est obligé de mettre des pointeurs vers le conteneur et s'inquiète d'une suppression. Je conviens que le fait d'avoir des objets de tailles différentes dans le même conteneur nuira aux performances.
David Gruzman
Les fonctions virtuelles sont réparties en fonction du type d'objet que vous possédez. Le point de l' ensemble des constructeurs est que ne pas un objet encore . Lié à la raison pour laquelle vous ne pouvez pas avoir de fonctions virtuelles statiques: pas non plus d'objet.
MSalters
Je peux dire que le modèle de conteneur de classe n'a pas besoin d'un objet, mais d'une fabrique de classes, et le constructeur en est une partie naturelle.
David Gruzman