Insertion de flux Surcharge sans violer cacher l'information?
-
24-10-2019 - |
Question
J'utilise yaml-cpp pour un projet. Je veux surcharger les opérateurs de <<
et >>
pour certaines classes, mais je vais avoir un problème avec la façon de grappling faire « correctement ». Prenez la classe Note
, par exemple. Il est ennuyeux assez:
class Note {
public:
// constructors
Note( void );
~Note( void );
// public accessor methods
void number( const unsigned long& number ) { _number = number; }
unsigned long number( void ) const { return _number; }
void author( const unsigned long& author ) { _author = author; }
unsigned long author( void ) const { return _author; }
void subject( const std::string& subject ) { _subject = subject; }
std::string subject( void ) const { return _subject; }
void body( const std::string& body ) { _body = body; }
std::string body( void ) const { return _body; }
private:
unsigned long _number;
unsigned long _author;
std::string _subject;
std::string _body;
};
L'opérateur <<
est facile sauce. Dans le .h
:
YAML::Emitter& operator << ( YAML::Emitter& out, const Note& v );
Et dans le .cpp
:
YAML::Emitter& operator << ( YAML::Emitter& out, const Note& v ) {
out << v.number() << v.author() << v.subject() << v.body();
return out;
}
Pas de sueur. Ensuite, je vais déclarer l'opérateur >>
. Dans le .h
:
void operator >> ( const YAML::Node& node, Note& note );
Mais dans le .cpp
je reçois:
void operator >> ( const YAML::Node& node, Note& note ) {
node[0] >> ?
node[1] >> ?
node[2] >> ?
node[3] >> ?
return;
}
Si j'écris des choses comme node[0] >> v._number;
alors je aurais besoin de changer le CV-qualifié pour faire tous les champs de Note
public
(qui contrecarre tout ce que j'appris (par des professeurs, des livres, et de l'expérience))) sur le masquage de données.
Je me sens comme faire node[0] >> temp0; v.number( temp0 );
partout est non seulement fastidieuse, sujette à l'erreur, et laid, mais inutile (ce avec les copies supplémentaires).
Alors je suis sage: J'ai essayé de déplacer ces deux opérateurs dans la classe Note
elle-même, et les déclarer comme friend
s, mais le compilateur (GCC 4.4) n'a pas comme ça:
src / note.h: 44: Erreur: « YAML :: & Remarque :: Emitter opérateur << (YAML :: & Emitter, const Note &) » doivent prendre un seul argument
src / note.h: 45: Erreur: « vide Remarque :: operator >> (const YAML :: node &, Note &) » doit prendre un seul argument
Question: Comment puis-je "correctement" surcharge l'opérateur >>
pour une classe
- Sans violer le principe de cacher l'information?
- Sans la copie excessive?
La solution
La façon typique de le faire sans violer l'encapsulation est de rendre l'opérateur >> une fonction ami. Il devait y avoir un problème de syntaxe avec votre déclaration d'un opérateur ami (pas clair exactement du message d'erreur). Je ne me YAML, mais de votre question qui suit est le jist de celui-ci:
class Note{
...
friend void operator >> ( const YAML::Node& node, Note& note );
....
};
void operator >> ( const YAML::Node& node, Note& note ){
node[0] >> note._number;
node[1] >> note._author;
node[2] >> note._subject;
node[3] >> note._body;
}
Une fonction ami a les mêmes droits d'accès aux membres privés en fonction de membre.
Vous pouvez déclarer setters pour toutes les données membres, mais la méthode de fonction ami est plus propre.
Autres conseils
J'aime utiliser une méthode d'assistance. Puisque la méthode fait partie de la classe, il aura un accès complet à tous les domaines privé:
class Note {
public:
void read(const YAML::Node& node)
{
node >> ...;
}
};
et alors operator>>
juste avant l'appel:
const YAML::Node &operator >> ( const YAML::Node& node, Note& note ) {
note.read(node);
return node;
}
Vous définissez d'autres méthodes setter dans Note
, tels que
void number(YAML::Immitter& e) { e>>_number; }
etc, et vous définissez ensuite la >>
syntaxe sucre
void operator >> ( YAML::Immitter& e, Note& note ) {
note.number(e);
note.author(e);
note.subject(e);
note.body(e);
}
Je ne suis pas au courant de l'espace de noms YAML que vous utilisez (je sais yaml
mais je ne l'ai jamais manié en C ++), mais qui est à peu près comment vous doIT avec des flux normaux (à l'exception des types de retour de void
; -), et je suis sûr qu'il peut être facilement adapté à vos besoins
Votre classe a déjà des méthodes setter. Il suffit d'utiliser pour lire les temporaires valeurs et d'utiliser les méthodes de réglage pour configurer l'objet:
void operator >> ( const YAML::Emitter& node, Note& note ) {
unsigned long number;
unsigned long author;
// ...
node[0] >> number;
node[1] >> author;
// ... everything properly read, edit the node:
node.number(number);
node.author(author);
// ...
return;
}
D'autres commentaires: Une classe qui a setters / getters pour tous les attributs est à peine encapsulé. Vous donnez aux utilisateurs le même niveau d'accès que si vos champs étaient réellement publics (avec le seul avantage que vous pouvez ajouter une vérification à une date ultérieure, mais encore, l'encapsulation est faible).
Sur les solutions qui suggèrent d'ajouter une méthode de membre qui prend le nœud YAML, qui ajoutera une dépendance supplémentaire à tous les utilisateurs de votre classe. Alors que vous pouvez utiliser des déclarations avant pour éviter de les forcer à inclure les en-têtes YAML, vous ne serez pas en mesure de tirer une bibliothèque avec votre Note
à utiliser dans un autre projet qui n'utilise pas YAML facilement.
Le potentiel gaspillage l'utilisation des ressources va probablement être très limité. Là encore, comme toujours, la première mesure et ensuite essayer de résoudre les problèmes si vous en avez.
Eh bien, voici une idée que vous pourriez envisager. Le problème que vous dites que vous avez avec le non-ami, non-membre << fonction est qu'elle implique beaucoup de déclarations tmp. Avez-vous envisagé encapsulant le concept et la construction d'un composant réutilisable autour d'elle? Utilisez pourrait ressembler à quelque chose comme ceci:
inputter& operator >> (inputter& in, my_type & obj)
{
input_helper<my_type> helper(obj);
in >> helper.setter(&my_type::number);
in >> helper.setter(&my_type::subject);
// etc
}
La responsabilité de input_helper
est simplement de fournir la fonction modèle setter()
qui retourne un objet qui lit simplement la valeur et appelle le compositeur avec elle, la création de la variable temporaire nécessaire. Code comme ceci nécessiterait une certaine familiarité intime avec des modèles, mais ne serait pas particulièrement difficile. Ne peut pas penser complètement droite en ce moment - peut-être un rhume - ou je serais probablement capable de taper tout simplement. Peut-être que quelque chose sorte de comme ceci:
template < typename T >
struct input_helper
{
input_helper(T & t) : obj(t) {}
template < typename V >
struct streamer
{
streamer(T & t, void (T::*f)(V const&)) : obj(t), fun(f) {}
template < typename Stream >
Stream& read_from(Stream & str) const // yeah, that's right...const; you'll be using a temporary.
{
V v;
str >> v;
obj.(*fun)(v);
return str;
}
private: // you know the drill...
}
template < typename V >
streamer setter(void (T::*fun)(V const&))
{
return streamer(obj, fun);
}
private:
T & obj;
};
// etc... operator >> (blah blah) { return setter.read_from(stream); }
Il y a sûrement toutes sortes d'erreurs dans cela, mais il devrait vous donner l'idée. Il faudrait aussi plus de travail à généraliser.