web-dev-qa-db-fra.com

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

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 la 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 C++ acceptable? Sinon, existe-t-il une bibliothèque tierce comme boost qui pourrait m'aider à contourner ce problème?

Merci d'avoir lu ceci !

Anthony

75
BlueTrin

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 pour abstraire des classes:

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 entraînerait le problème de découpage d'objet .

115
Georg Fritzsche

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 les valeurs de magasin std :: vector (c'est-à-dire les instances). Si vous voulez le faire, vous devrez créer un vecteur de pointeurs vers le type de classe abstrait.

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 comprendre que C++ et C # ont très peu en commun. Si vous avez l'intention d'apprendre le C++, vous devriez penser à partir de zéro et lire un bon tutoriel C++ dédié tel que Accelerated C++ par Koenig et Moo.

21
anon

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 IS Une relation 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>";
    }
};
6
Sergey Teplyakov

L'alternative traditionnelle consiste à utiliser un vector de pointeurs, comme déjà indiqué.

Pour ceux qui apprécient, Boost est livré avec une bibliothèque très intéressante: Pointer Containers qui est parfaitement adapté à la tâche et vous libère des différents problèmes impliqués par les pointeurs:

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

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

Maintenant, il y a une troisiè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 variation de l'idiome Pimpl enrichi par un modèle Strategy.

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. Ce n'est donc peut-être pas ce que vous souhaitez.

6
Matthieu M.

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

Vous pouvez utiliser un pointeur comme suggéré.

2
kennytm

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 pourrez 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 d'intégrer 30 dans votre vecteur de 20

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

1
Eric