27 votes

Puis-je avoir polymorphes conteneurs avec la valeur sémantique en C++?

En règle générale, je préfère utiliser de la valeur plutôt que le pointeur de la sémantique dans le C++ (c'est à dire à l'aide de vector<Classe> au lieu de vector<Classe*>). Habituellement, la légère perte de performance est plus que de ne pas avoir à penser à supprimer les objets alloués dynamiquement.

Malheureusement, la valeur des collections ne fonctionne pas lorsque vous souhaitez stocker une variété de types d'objets qui dérivent toutes d'une base commune. Voir l'exemple ci-dessous.

#include <iostream>

using namespace std;

class Parent
{
    public:
    	Parent() : parent_mem(1) {}
    	virtual void write() { cout << "Parent: " << parent_mem << endl; }
    	int parent_mem;
};

class Child : public Parent
{
    public:
    	Child() : child_mem(2) { parent_mem = 2; }
    	void write() { cout << "Child: " << parent_mem << ", " << child_mem << endl; }

    	int child_mem;
};

int main(int, char**)
{
    // I can have a polymorphic container with pointer semantics
    vector<Parent*> pointerVec;

    pointerVec.push_back(new Parent());
    pointerVec.push_back(new Child());

    pointerVec[0]->write();	
    pointerVec[1]->write();	

    // Output:
    //
    // Parent: 1
    // Child: 2, 2

    // But I can't do it with value semantics

    vector<Parent> valueVec;

    valueVec.push_back(Parent());
    valueVec.push_back(Child());	// gets turned into a Parent object :(

    valueVec[0].write();	
    valueVec[1].write();	

    // Output:
    // 
    // Parent: 1
    // Parent: 2

}

Ma question est: puis-je avoir mon gâteau (valeur sémantique) et le manger aussi (polymorphes conteneurs)? Ou dois-je utiliser des pointeurs?

21voto

1800 INFORMATION Points 55907

Étant donné que les objets de différentes classes de tailles différentes, vous finiriez en cours d'exécution dans le découpage de problème si vous les stocker en tant que valeurs.

Une solution raisonnable est d'entreposer le contenant sécuritaire des pointeurs intelligents. J'ai l'habitude d'utiliser boost::shared_ptr qui est sûr pour stocker dans un récipient. Notez que std::auto_ptr est pas.

vector<shared_ptr<Parent>> vec;
vec.push_back(shared_ptr<Parent>(new Child()));

shared_ptr utilise le comptage de référence afin de ne pas supprimer l'instance sous-jacente jusqu'à ce que toutes les références sont supprimés.

8voto

ben Points 826

Oui, vous le pouvez.

Le coup de pouce.ptr_container bibliothèque fournit des polymorphes de la valeur sémantique des versions de la norme des containers. Vous n'avez qu'à passer un pointeur vers un segment de mémoire alloué par l'objet, et le conteneur va prendre possession et toutes les autres opérations d'apporter de la valeur sémantique , à l'exception de récupération de la propriété, ce qui vous donne presque tous les avantages de la valeur sémantique à l'aide d'un pointeur intelligent.

7voto

0124816 Points 804

Je voulais juste faire remarquer que le vecteur<Foo> est généralement plus efficace que le vecteur<Foo*>. Dans un vector<Foo>, tous les Foos sera adjacents les uns aux autres dans la mémoire. En supposant un froid TLB et la mise en cache, la première lecture ajouter la page à la TLB et tirez un morceau du vecteur dans la L# caches; les lectures suivantes utiliseront le chaud cache et chargé TLB, avec parfois des défauts de cache et de moins en moins fréquentes TLB défauts.

Cela contraste avec un vector<Foo*>: Que vous remplissez le vecteur, on obtient des Foo*'s de votre allocateur de mémoire. En supposant que votre allocation n'est pas très intelligent, (tcmalloc?) ou vous remplissez le vecteur lentement au fil du temps, de l'emplacement de chaque Foo est susceptible d'être très éloignés l'un de l'autre Foos: peut-être juste par des centaines d'octets, peut-être mégaoctets à part.

Dans le pire des cas, comme vous le balayage par l'intermédiaire d'un vecteur<Foo*> et de déférence chaque pointeur, vous devrez vous acquitter d'une TLB faute et cache miss -- ce sera à la fin d'un beaucoup plus lent que si vous aviez un vector<Foo>. (Eh bien, vraiment au pire des cas, chaque Foo ont été transférées sur le disque, et de tous les lire encourt une recherche de disque() et read() pour déplacer la page dans la RAM.)

Alors, gardez le à l'aide de vector<Foo> chaque fois que de besoin. :-)

3voto

Adam Hollidge Points 544

Vous pouvez également envisager de boost::any. Je l'ai utilisé pour hétérogène des conteneurs. Lors de la lecture de la valeur de retour, vous devez effectuer une any_cast. Il va jeter un bad_any_cast si elle échoue. Si cela se produit, vous pouvez vous rattraper et de passer à côté du type.

Je crois qu'il va jeter un bad_any_cast si vous essayez de any_cast une classe dérivée de sa base. Je l'ai essayé:

  // But you sort of can do it with boost::any.

  vector<any> valueVec;

  valueVec.push_back(any(Parent()));
  valueVec.push_back(any(Child()));        // remains a Child, wrapped in an Any.

  Parent p = any_cast<Parent>(valueVec[0]);
  Child c = any_cast<Child>(valueVec[1]);
  p.write();
  c.write();

  // Output:
  //
  // Parent: 1
  // Child: 2, 2

  // Now try casting the child as a parent.
  try {
  	Parent p2 = any_cast<Parent>(valueVec[1]);
  	p2.write();
  }
  catch (const boost::bad_any_cast &e)
  {
      cout << e.what() << endl;
  }

  // Output:
  // boost::bad_any_cast: failed conversion using boost::any_cast

Tout cela étant dit, je voudrais aussi aller le shared_ptr route de la première! Juste pensé que cela pourrait être d'un certain intérêt.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X