Question

En C++, de quelles alternatives ai-je pour exposer une collection, du point de vue des performances et de l'intégrité des données ?

Mon problème est que je souhaite renvoyer une liste interne de données à l'appelant, mais je ne souhaite pas en générer une copie.Thant me laisse soit renvoyer une référence à la liste, soit un pointeur vers la liste.Cependant, je ne suis pas fou de laisser l'appelant modifier les données, je veux juste le laisser lire les données.

  • Dois-je choisir entre performances et intégrité des données ?
  • Si tel est le cas, vaut-il en général mieux aller dans un sens ou est-ce particulier au cas ?
  • Existe-t-il d'autres alternatives ?
Était-ce utile?

La solution

La réponse de RichQ est une technique raisonnable si vous utilisez un tableau, un vecteur, etc.

Si vous utilisez une collection qui n'est pas indexée par des valeurs ordinales...ou je pense que tu il faudra peut-être à un moment donné dans un futur proche...alors vous voudrez peut-être envisager d'exposer votre (vos) propre(s) type(s) d'itérateur et les begin()/end() méthodes :

class Blah
{
public:
   typedef std::vector<mydata> mydata_collection;
   typedef myDataCollection::const_iterator mydata_const_iterator;

   // ...

   mydata_const_iterator data_begin() const 
      { return myPreciousData.begin(); }
   mydata_const_iterator data_end() const 
      { return myPreciousData.end(); }

private:
   mydata_collection  myPreciousData;
};

... que vous pouvez ensuite utiliser normalement :

Blah blah;
for (Blah::mydata_const_iterator itr = blah.data_begin();
   itr != blah.data_end();
   ++itr)
{
   // ...
}

Autres conseils

Souvent, l’appelant souhaite accéder simplement pour parcourir la collection.Prenez une page du livre de Ruby et faites de l'itération un aspect privé de votre cours.

#include <algorithm>
#include <boost/function.hpp>

class Blah
{
  public:
     void for_each_data(const std::function<void(const mydata&)>& f) const
     {
         std::for_each(myPreciousData.begin(), myPreciousData.end(), f);
     }

  private:
     typedef std::vector<mydata> mydata_collection;
     mydata_collection  myPreciousData;
};

Avec cette approche, vous n'exposez rien sur vos composants internes, c'est-à-direque toi même avoir une collection.

Peut-être quelque chose comme ça ?

const std::vector<mydata>& getData()
{
  return _myPrivateData;
}

L’avantage ici est que c’est très, très simple et aussi sûr qu’en C++.Vous pouvez lancer ceci, comme le suggère RobQ, mais vous ne pouvez rien faire qui empêcherait quelqu'un de le faire si vous ne copiez pas.Ici, il faudrait utiliser const_cast, ce qui est assez facile à repérer si vous le recherchez.

Les itérateurs, quant à eux, peuvent obtenir à peu près la même chose, mais c'est plus compliqué.Le seul avantage supplémentaire de l'utilisation d'itérateurs ici (auquel je peux penser) est que vous pouvez avoir une meilleure encapsulation.

L'utilisation d'une référence const ou d'un pointeur partagé n'est utile que si le contenu de la collection sous-jacente ne change pas au fil du temps.

Considérez votre conception.L’appelant a-t-il vraiment besoin de voir le tableau interne ?Pouvez-vous restructurer le code afin que l'appelant indique à l'objet quoi faire avec le tableau ?Par exemple, si l'appelant a l'intention de rechercher dans le tableau, l'objet propriétaire pourrait-il le faire ?

Vous pouvez transmettre une référence au vecteur de résultat à la fonction.Sur certains compilateurs, cela peut entraîner un code légèrement plus rapide.

Je recommanderais d'essayer de repenser d'abord, d'opter ensuite pour une solution propre, puis d'optimiser les performances en troisième (si nécessaire).

L'un des avantages des solutions de @Shog9 et de @RichQ est qu'elles dissocient le client de la mise en œuvre de la collection.

Si vous décidez de changer votre type de collecte pour autre chose, vos clients continueront de travailler.

Ce que vous voulez, c'est un accès en lecture seule sans copier l'intégralité de la quantité de données.Vous avez plusieurs options.

Tout d'abord, vous pouvez simplement renvoyer une référence const à votre conteneur de données, comme suggéré ci-dessus :

const std::vector<T>& getData() { return mData; }

Cela présente l’inconvénient du caractère concret :vous ne pouvez pas modifier la façon dont vous stockez les données en interne sans modifier l'interface de votre classe.

Deuxièmement, vous pouvez renvoyer des pointeurs const-ed vers les données réelles :

const T* getDataAt(size_t index)
{
   return &mData[index];
}

C'est un peu plus agréable, mais nécessite également que vous fournissiez un appel getNumItems et que vous vous protégiez contre les index hors limites.De plus, la constance de vos pointeurs est facilement supprimée et vos données sont désormais en lecture-écriture.

Une autre option consiste à fournir une paire d’itérateurs, ce qui est un peu plus complexe.Cela présente les mêmes avantages que les pointeurs, mais ne nécessite pas (nécessairement) de fournir un appel getNumItems, et nécessite beaucoup plus de travail pour dépouiller les itérateurs de leur constance.

Le moyen le plus simple de gérer cela consiste probablement à utiliser une gamme Boost :

typedef vector<T>::const_iterator range_iterator_type;
boost::iterator_range< range_iterator_type >& getDataRange()
{
    return boost::iterator_range(mData.begin(), mData.end());
}

Cela présente l'avantage que les plages soient composables, filtrables, etc., comme vous pouvez le voir sur le site web.

Utiliser const est un choix raisonnable.Vous souhaiterez peut-être également consulter la bibliothèque boost C++ pour leur implémentation de pointeur partagé.Il offre les avantages des pointeurs, c'est-à-direvous devrez peut-être renvoyer un pointeur partagé sur "null", ce qu'une référence ne permettrait pas.

http://www.boost.org/doc/libs/1_36_0/libs/smart_ptr/smart_ptr.htm

Dans votre cas, vous feriez en sorte que le type const du pointeur partagé interdise les écritures.

Si tu as un std::list de données anciennes (ce que .NET appellerait des « types de valeur »), alors renvoyer une référence const à cette liste conviendra (en ignorant les choses perverses comme const_cast)

Si tu as un std::list de pointeurs (ou boost::shared_ptr's), cela vous empêchera seulement de modifier la collection, pas les éléments dans la collection.Mon C++ est trop rouillé pour pouvoir vous donner la réponse à ce stade :-(

Je suggère d'utiliser des rappels dans le sens de EnumChildWindows.Vous devrez trouver des moyens pour empêcher l'utilisateur de modifier vos données.Peut-être utiliser un const pointeur/référence.

D'un autre côté, vous pouvez transmettre une copie de chaque élément à la fonction de rappel en écrasant la copie à chaque fois.(Vous ne souhaitez pas générer une copie de l’intégralité de votre collection.Je suggère seulement de faire une copie d'un élément à la fois.Cela ne devrait pas prendre beaucoup de temps/mémoire).

MyClass tmp;
for(int i = 0; i < n; i++){
    tmp = elements[i];
    callback(tmp);
}

Les deux articles suivants développent certains des problèmes impliqués et la nécessité de l'encapsulation des classes de conteneurs.Bien qu’ils ne fournissent pas une solution complète et efficace, ils conduisent essentiellement à la même approche que celle proposée par Shog9.

Partie 1: Encapsulation et vampires
Partie 2 (une inscription gratuite est désormais requise pour lire ceci) : Repérage d'épaves de train
par Kevlin Henney

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top