Comment écrire du code polymorphique en C++?
je suis en train d'écrire un morceau de code avec des exigences élevées sur la performance où je dois gérer un grand nombre d'objets d'une manière polymorphe. Disons que j'ai une classe A et une classe B qui est dérivée de A. je pouvais maintenant créer un vecteur de B:s comme ceci
vector<A*> a(n);
for(int i = 0; i < n; i++)
a[i] = new B();
mais si n est grand (dans l'ordre de 10^6 ou plus dans mon cas) cela nécessiterait de très nombreux appels à de nouveaux objets et de plus les n pourraient potentiellement être répartis sur toute ma mémoire principale résultant en une très mauvaise cache performance. Quelle serait la bonne façon de faire face à cette situation? Je pense à faire quelque chose comme ce qui suit pour avoir tous les objets dans une région de mémoire contiguë.
B* b = new B[n];
vector<A*> a(n);
for(int i = 0; i < n; i++)
a[i] = b + i;
mais un problème est de savoir comment libérer la mémoire allouée par new B[n] si b n'est plus disponible (mais nous avons encore un). J'ai juste appris que le fait d'essayer
delete[] a[0];
n'est pas une bonne idée...
4 réponses
Vous pouvez utiliser le placement nouveau pour construire un objet à un emplacement mémoire particulier:
vector<A*> a(n);
for(int i = 0; i < n; i++)
a[i] = new(storage + i*object_size) B();
// and invoke the destructor manually to release the object (assuming A has a virtual destructor!)
a[i]->~A();
mais vous ne pouvez pas résoudre le "vrai" problème sans renoncer au stockage continu: si un objet est libéré, il causera un trou dans le tas, provoquant ainsi une forte fragmentation au fil du temps. Vous ne pouviez garder trace des objets libérés et réutiliser le stockage.
si vous êtes sûr que ce ne seront que des objets de type B
pourquoi ne pas utiliser un vecteur parallèle:
vector<B> storage(n);
vector<A*> pointers(n);
for(int i = 0; i < n; i++)
pointers[i] = &storage[i];
si vous voulez garder tous vos objets dans une mémoire contiguë et, en même temps, éviter d'utiliser une indication indirecte (un vecteur de pointeurs de classe de base), vous pouvez utiliser un conteneur de type union, par exemple un vecteur boost::variant
. Cela suppose, bien entendu, qu'il existe un nombre limité et connu de classes dérivées et que leur taille est comparable. L'inconvénient est que chaque élément du vecteur prendra autant de mémoire que la plus grande classe dérivée, indépendamment de leur classe réelle (et il suppose également vos cours sont pas chers à copier). Les avantages sont que vous avez le stockage hétérogène contigu des objets polymorphiques, type-sécurité, et aucune indirecte lors de l'accès aux éléments. Voici un exemple de base avec boost::variant
et trois classes A
,B
,C
, où les deux B
et C
hérite de A
, et ils sont tous polymorphes (et, bien sûr, cela pourrait être beaucoup plus agréable avec un peu de sucre-revêtement et/ou quelque chose de plus spécialisé pour votre but, pas boost::variant
ce qui n'est pas vraiment approprié à cette fin):
#include <iostream>
#include <vector>
#include <boost/variant/variant.hpp>
#include <boost/variant/apply_visitor.hpp>
struct A {
int v1;
A(int aV1 = 0) : v1(aV1) { };
virtual ~A() { };
virtual void print() { std::cout << "A print " << v1 << std::endl; };
struct get_ref {
typedef A& result_type;
template <class T>
A& operator()(T& obj) const { return obj; };
};
};
struct B : A {
float f1;
B(float aF1 = 0.0) : A(42), f1(aF1) { };
~B() { };
virtual void print() { std::cout << "B print " << f1 << std::endl; };
};
struct C : A {
double d1;
C(double aD1 = 0.0) : A(42), d1(aD1) { };
~C() { };
virtual void print() { std::cout << "C print " << d1 << std::endl; };
};
int main() {
typedef boost::variant<A,B,C> value_type;
typedef std::vector< value_type > vect_type;
vect_type arr(15);
int i=0;
for(;i<5;++i) arr[i] = A(31);
for(;i<10;++i) arr[i] = B(0.2);
for(;i<15;++i) arr[i] = C(0.4);
for(vect_type::iterator it = arr.begin(); it != arr.end(); ++it)
boost::apply_visitor(A::get_ref(), *it).print();
std::cout << "value_type has size of " << sizeof(value_type) << std::endl;
std::cout << "compared to: sizeof(A)=" << sizeof(A) << " sizeof(B)=" << sizeof(B) << " sizeof(C)=" << sizeof(C) << std::endl;
return 0;
};
vous avez juste à stocker les pointeurs retournés de new[] quelque part, et les supprimer[] plus tard. D'un autre vecteur, par exemple.