Question

J'ai une simple classe de conteneur avec un constructeur de copie.

Avez-vous recommandons d'utiliser des accesseurs, ou accéder aux variables membres directement?

public Container 
{
   public:
   Container() {}

   Container(const Container& cont)          //option 1
   { 
       SetMyString(cont.GetMyString());
   }

   //OR

   Container(const Container& cont)          //option 2
   {
      m_str1 = cont.m_str1;
   }

   public string GetMyString() { return m_str1;}       

   public void SetMyString(string str) { m_str1 = str;}

   private:

   string m_str1;
}
  • Dans l'exemple, tout le code est en ligne, mais dans notre code réel, il n'y a pas de code en ligne.

Mise à jour (29 septembre 09):

Certaines de ces réponses sont cependant ils semblent bien écrit pour obtenir le point de manquer cette question:

  • est simple exemple pour discuter de l'aide artificielle getters / setters vs les variables

  • listes de initialiseur ou les fonctions de validateur privées ne sont pas vraiment partie de cette question. Je me demande si l'une conception va rendre le code plus facile à maintenir et à développer.

  • Certains ppl se concentrent sur la chaîne dans cet exemple mais il est juste un exemple, imaginez c'est un autre objet à la place.

  • Je ne suis pas préoccupé par la performance. nous ne sommes pas la programmation sur le PDP-11

Était-ce utile?

La solution

Prévoyez-vous que la façon dont la chaîne est retournée, par exemple. espace blanc garni, null cochée, etc.? Même avec SetMyString (), si la réponse est oui, vous êtes mieux avec les méthodes d'accès puisque vous ne devez pas changer votre code dans des endroits zillion, mais il suffit de modifier ces méthodes getter et setter.

Autres conseils

EDIT: Répondre à la question modifiée:)

  

est simple exemple parvint à    discuter de l'utilisation getters / setters vs    Variables

Si vous avez une simple collection de variables, qui ne nécessitent aucune sorte de validation, ni un traitement supplémentaire, vous pouvez envisager d'utiliser un POD à la place. De FAQ de Stroustrup:

  

Une classe bien conçu présente un environnement propre   et une interface simple à ses utilisateurs,    cacher sa représentation et économie   ses utilisateurs d'avoir à connaître   cette représentation. Si la   la représentation ne doit pas être caché -   dire, parce que les utilisateurs devraient pouvoir   changer tout membre de données comme ils   comme - vous pouvez penser de cette classe   « Juste une structure de données ancienne plaine »

En bref, ce n'est pas JAVA. vous ne devriez pas écrire getters / setters simples parce qu'ils sont aussi mauvais que les exposer eux-mêmes variables.

  

Listes de initialiseur ou les fonctions de validateur privées ne sont pas vraiment   une partie de cette question. je me demande   si l'une conception fera le code   plus facile à maintenir et à se développer.

Si vous copiez variables d'un autre objet, l'objet source doit être dans un état valide. Comment le mauvais objet source formé se est construit en premier lieu ?! Ne devraient pas faire le travail des constructeurs de validation? ne sont pas les fonctions membres modification responsable du maintien de la classe invariant en validant l'entrée? Pourquoi voudriez-vous valider un objet « valide » dans un constructeur de copie?

  

Je ne suis pas préoccupé par la performance. nous ne sommes pas la programmation sur le PDP-11

est sur le style le plus élégant, mais en C ++ le code le plus élégant a les meilleures caractéristiques de performance habituellement.


Vous devez utiliser un initializer list. Dans votre code, m_str1 est construit par défaut puis attribué une nouvelle valeur. Votre code pourrait être quelque chose comme ceci:

class Container 
{
public:
   Container() {}

   Container(const Container& cont) : m_str1(cont.m_str1)
   { }

   string GetMyString() { return m_str1;}       
   void SetMyString(string str) { m_str1 = str;}
private:
   string m_str1;
};

@cbrulak Vous ne devriez pas valider l'OMI cont.m_str1 dans le copy constructor. Ce que je fais, est de valider les choses en constructors. Validation copy constructor signifie que vous vous copiez un objet mal formé en premier lieu, par exemple:

Container(const string& str) : m_str1(str)
{
    if(!valid(m_str1)) // valid() is a function to check your input
    {
        // throw an exception!
    }
}

Vous devez utiliser une liste d'initialiseur, puis la question devient dénuée de sens, comme dans:

Container(const Container& rhs)
  : m_str1(rhs.m_str1)
{}

Il y a une grande section Imperfect C ++ qui explique tout sur les listes membres Initializer, et sur la façon dont vous peut les utiliser en combinaison avec const et / ou références à rendre votre code plus sûr.

Modifier : un exemple montrant la validation et const:

class Container
{
public:
  Container(const string& str)
    : m_str1(validate_string(str))
  {}
private:
  static const string& validate_string(const string& str)
  {
    if(str.empty())
    {
      throw runtime_error("invalid argument");
    }
    return str;
  }
private:
  const string m_str1;
};

Comme il est écrit en ce moment (sans qualification de l'entrée ou de sortie) votre getter et setter (accesseur et mutateur, si vous préférez) accomplissons absolument rien, vous pourriez tout aussi bien faire la chaîne publique et faire avec il.

Si le code réel fait vraiment qualifier la chaîne, alors les chances sont assez bonnes que ce que vous avez affaire à n'est pas correctement une chaîne du tout - au lieu, il est juste quelque chose qui ressemble beaucoup à une chaîne. Qu'est-ce que vous faites vraiment dans ce cas abuse du système de type, exposant une sorte de chaîne, lorsque le type réel est seulement quelque chose un peu comme une chaîne. Vous êtes alors fournir le poseur d'essayer de faire appliquer les restrictions du type réel a comparé à une véritable chaîne.

Quand vous regardez de cette direction, la réponse devient assez évidente: plutôt qu'une chaîne, avec un compositeur pour faire l'acte de chaîne comme un autre (plus restreint) de type, ce que vous devez faire à la place est de définir une réelle classe pour le type que vous voulez vraiment. Après avoir correctement défini cette classe, vous faites une instance de public. Si (comme cela semble être le cas ici), il est raisonnable de lui attribuer une valeur qui commence comme une chaîne, cette classe doit contenir un opérateur d'affectation qui prend une chaîne comme argument. Si (comme cela semble aussi être le cas ici), il est raisonnable de convertir ce type à une chaîne dans certaines circonstances, il peut également inclure l'opérateur jeté qui produit une chaîne comme résultat.

Cela donne une réelle amélioration par rapport à l'aide d'un setter et getter dans une classe environnante. Tout d'abord, quand vous mettez les dans une classe environnante, il est facile pour le code dans cette classe pour contourner le getter / setter, perdant l'application de ce que le compositeur était censé faire respecter. En second lieu, il maintient une notation normale prospectifs. L'utilisation d'un getter et un setter forces vous d'écrire du code qui est tout simplement laid et difficile à lire.

L'un des atouts majeurs d'une classe de chaîne en C ++ utilise la surcharge d'opérateur de sorte que vous pouvez remplacer quelque chose comme:

strcpy(strcat(filename, ".ext"));

avec:

filename += ".ext";

pour améliorer la lisibilité. Mais regardez ce qui se passe si cette chaîne fait partie d'une classe qui nous oblige à passer par un getter et setter:

some_object.setfilename(some_object.getfilename()+".ext");

Si quoi que ce soit, le code C est en fait plus lisible que ce gâchis. D'autre part, pensez à ce qui se passe si nous faisons du bon travail, avec un objet public d'une classe qui définit une chaîne de l'opérateur et l'opérateur =:

some_object.filename += ".ext";

Nice, simple et lisible, tout comme il devrait être. Mieux encore, si nous devons appliquer quelque chose sur la chaîne, on peut vérifier que cette petite classe, nous avons vraiment qu'à regarder un ou deux endroits spécifiques, bien connus (opérateur =, peut-être un ctor ou deux pour cette catégorie) à savoir qu'il est toujours appliquée -. une histoire complètement différent de celui que nous utilisons un setter pour essayer de faire le travail

Demandez-vous quels sont les coûts et les avantages.

Coût: frais généraux plus élevés d'exécution. Appel de fonctions virtuelles cteurs est une mauvaise idée, mais setters et getters sont peu susceptibles d'être virtuel.

Avantages: si le setter / getter fait quelque chose de compliqué, vous n'êtes pas le code répéter; si elle fait quelque chose unintuitive, vous n'êtes pas oublier de le faire.

Le rapport coût / bénéfice sera différent pour les classes différentes. Une fois que vous avez constaté que le rapport, utilisez votre jugement. Pour les classes immuables, bien sûr, vous n'avez pas setters, et vous n'avez pas besoin getters (en tant que membres const et les références peuvent être publiques comme personne ne peut changer / rasseoir eux).

Il n'y a pas de solution miracle comme la façon d'écrire le constructeur de copie. Si votre classe ne dispose que les membres qui fournissent un constructeur de copie qui crée instances qui ne partagent pas l'état (ou du moins ne semblent pas le faire) à l'aide d'une liste d'initialiseur est une bonne façon.

Sinon, vous devrez penser réellement.

struct alpha {
   beta* m_beta;
   alpha() : m_beta(new beta()) {}
   ~alpha() { delete m_beta; }
   alpha(const alpha& a) {
     // need to copy? or do you have a shared state? copy on write?
     m_beta = new beta(*a.m_beta);
     // wrong
     m_beta = a.m_beta;
   }

Notez que vous pouvez contourner le segfault potentiel en utilisant smart_ptr -. Mais vous pouvez avoir beaucoup de plaisir débogage des bogues résultant

Bien sûr, il peut être encore plus drôle.

  • Membres qui sont créés à la demande.
  • new beta(a.beta) est mauvais dans le cas où vous présenter en quelque sorte polymorphisme.

... une vis sinon -. S'il vous plaît toujours penser lors de l'écriture d'un constructeur de copie

Pourquoi avez-vous besoin accesseurs à tous?

:) Simple - Ils conservent invariants - dire garantit fait votre classe, tels que « MaChaîne a toujours un nombre pair de caractères »

.

Si elle est appliquée comme prévu, votre objet est toujours dans un état valide - si une copie peut très bien membre à membre copier les membres directement sans crainte de casser aucune garantie. Il n'y a aucun avantage de passer l'état déjà validé par un autre cycle de validation de l'Etat.

Comme dit Arak, le mieux serait que vous utilisiez une liste d'initialisation.


Pas si simple (1):
Une autre raison d'utiliser getters / setters ne se fie pas sur les détails de mise en œuvre. Voilà une drôle d'idée pour une Ctor copie, lors de la modification des détails de mise en œuvre dont vous avez besoin presque toujours pour ajuster CDA de toute façon.


Pas si simple (2):
Pour me prouver le contraire, vous pouvez construire des invariants qui dépendent de l'instance elle-même, ou un autre facteur externe. Un (très contrieved) par exemple: « si le nombre de cas est encore, la longueur de chaîne est même, sinon il est étrange. » Dans ce cas, la Ctor copie aurait à jeter, ou d'ajuster la chaîne. Dans un tel cas, il pourrait aider à utiliser setters / getters - mais ce n'est pas le général CAS. Vous ne devriez pas déduire des règles générales de bizarreries.

Je préfère utiliser une interface pour les classes extérieures pour accéder aux données, au cas où vous voulez changer la façon dont il est récupéré. Toutefois, lorsque vous êtes dans le cadre de la classe et que vous voulez reproduire l'état interne de la valeur copiée, je partirais avec les membres de données directement.

Sans oublier que vous allez probablement économiser quelques appels de fonction si le getter ne sont pas inline.

Si vos getters sont (en ligne et) ne virtual, il n'y a pas points positifs ni négatifs en les utilisant WRT accès membre direct - il semble juste Goofy pour moi en termes de style, mais pas une grosse affaire de toute façon

Si vos getters sont virtuels, puis il les frais généraux ... mais quand même c'est exactement quand vous ne voulez les appeler, juste au cas où ils sont redéfinies dans une sous-classe -)

Il y a un test simple qui fonctionne pour de nombreuses questions de conception, celui-ci inclus. Ajouter des effets secondaires et voir ce qui casse

setter Suppose affecte non seulement une valeur, mais écrit également enregistrement d'audit, enregistre un message ou déclenche un événement. Voulez-vous y arriver pour chaque propriété lors de la copie objet? Probablement pas -. Donc, appeler setters dans le constructeur est logiquement mal (même si setters sont en fait seulement des missions)

Bien que je suis d'accord avec d'autres affiches qu'il ya beaucoup de C ++ niveau d'entrée « non-non » dans votre échantillon, mettre que sur le côté et répondre à votre question directement:

Dans la pratique, j'ai tendance à faire beaucoup, mais pas tous mes champs membres * du public pour commencer, puis les déplacer pour obtenir / set en cas de besoin.

Maintenant, je serai le premier à dire que ce n'est pas nécessairement une pratique recommandée, et de nombreux praticiens et abhorrer ce dire que tous les domaines doit avoir setters / getters.

Peut-être. Mais je trouve que, dans la pratique, ce n'est pas toujours nécessaire. Certes, il provoque des douleurs plus tard, quand je change un champ de public à un getter, et parfois, quand je sais ce que l'utilisation d'une classe aura, je lui donne set / get et rendre le champ dès le départ protégé ou privé.

YMMV

RF

  • vous appelez champs « variables » - Je vous encourage à utiliser ce terme que pour les variables locales dans une fonction / méthode
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top