J'ai une classe qui représente une liste de personnes.
class AddressBook
{
public:
AddressBook();
private:
std::vector<People> people;
}
Je veux permettre aux clients d'itérer sur le vecteur des personnes. La première pensée que j'ai eue était simplement:
std::vector<People> & getPeople { return people; }
Cependant, je ne veux pas divulguer les détails de l'implémentation au client . Je peux vouloir conserver certains invariants lorsque le vecteur est modifié, et je perds le contrôle de ces invariants lorsque je fuit l'implémentation.
Quelle est la meilleure façon de permettre l'itération sans fuir les internes?
begin()
etend()
sont dangereux car (1) ces types sont des itérateurs de vecteurs (classes) qui empêchent l'un de basculer vers un autre conteneur tel que aset
. (2) Si le vecteur est modifié (par exemple, développé ou certains éléments effacés), certains ou tous les itérateurs de vecteur pourraient avoir été invalidés.Réponses:
permettre l'itération sans fuite des internes est exactement ce que le modèle d'itérateur promet. Bien sûr, c'est principalement de la théorie, alors voici un exemple pratique:
Vous fournissez des standards
begin
et desend
méthodes, tout comme les séquences dans la STL et les implémentez simplement en les transmettant à la méthode du vecteur. Cela laisse fuir certains détails d'implémentation, à savoir que vous retournez un itérateur vectoriel, mais aucun client sensé ne devrait jamais en dépendre, donc ce n'est pas un problème. J'ai montré toutes les surcharges ici, mais bien sûr, vous pouvez commencer par fournir la version const si les clients ne doivent pas pouvoir modifier les entrées People. L'utilisation de la dénomination standard présente des avantages: toute personne lisant le code sait immédiatement qu'il fournit une itération `` standard '' et, en tant que tel, fonctionne avec tous les algorithmes courants, la plage basée sur les boucles, etc.la source
begin()
etend()
qui se transmet simplement aux vecteursbegin()
etend()
permet à l'utilisateur de modifier les éléments dans le vecteur lui-même, peut-être en utilisantstd::sort()
. Selon les invariants que vous essayez de conserver, cela peut être acceptable ou non. Fournirbegin()
etend()
, cependant, est nécessaire pour prendre en charge les boucles basées sur la plage C ++ 11.Si l'itération est tout ce dont vous avez besoin, alors peut-être qu'un wrapper
std::for_each
suffirait:la source
const
itération. Lefor_each()
est uneconst
fonction membre. Par conséquent, le membrepeople
est considéré commeconst
. Par conséquent,begin()
etend()
surchargera commeconst
. Par conséquent, ils renverrontconst_iterator
s àpeople
. Par conséquent,f()
recevra unPeople const&
. Écrirecbegin()
/cend()
ici ne changera rien, dans la pratique, mais en tant qu'utilisateur obsessionnel deconst
je pourrais dire que cela vaut toujours la peine, comme (a) pourquoi pas; c'est juste 2 caractères, (b) j'aime dire ce que je veux dire, au moins avecconst
, (c) ça protège contre un collage accidentel quelque part nonconst
, etc.Vous pouvez utiliser l'idiome pimpl et fournir des méthodes pour parcourir le conteneur.
Dans l'en-tête:
Dans la source:
De cette façon, si votre client utilise le typedef de l'en-tête, il ne remarquera pas le type de conteneur que vous utilisez. Et les détails de mise en œuvre sont complètement cachés.
la source
On pourrait fournir des fonctions membres:
Qui permettent l'accès sans exposer les détails d'implémentation (comme la contiguïté) et les utilisent au sein d'une classe d'itérateur:
Les itérateurs peuvent ensuite être retournés par le carnet d'adresses comme suit:
Vous auriez probablement besoin d'étoffer la classe des itérateurs avec des traits, etc. mais je pense que cela fera ce que vous avez demandé.
la source
si vous voulez une implémentation exacte des fonctions de std :: vector, utilisez l'héritage privé comme ci-dessous et contrôlez ce qui est exposé.
Modifier: Ceci n'est pas recommandé si vous souhaitez également masquer la structure de données interne, c'est-à-dire std :: vector
la source
vector
autres, que vous ne voulez jamais utiliser mais que vous devez néanmoins hériter?), et peut-être activement dangereux (et si la classe héritée paresseusement pouvait être supprimée par un pointeur vers ce type de base quelque part, mais elle [irresponsablement] ne protégeait pas contre la destruction de un obj dérivé via un tel pointeur, donc simplement le détruire est UB?)