web-dev-qa-db-fra.com

Conventions pour les méthodes d'accesseur (getters et setters) en C ++

Plusieurs questions sur les méthodes d'accesseur en C++ ont été posées sur SO, mais aucune n'a pu satisfaire ma curiosité sur la question.

J'essaie d'éviter les accesseurs chaque fois que possible, car, comme Stroustrup et d'autres programmeurs célèbres, je considère une classe avec beaucoup d'entre eux comme un signe de mauvais OO. En C++, je peux dans la plupart des cas ajouter plus de responsabilité à une classe ou utiliser le mot-clé friend pour les éviter. Pourtant, dans certains cas, vous avez vraiment besoin d'accéder à des membres de classe spécifiques.

Il existe plusieurs possibilités:

1. N'utilisez pas du tout d'accessoires

Nous pouvons simplement rendre publiques les variables membres respectives. Il s'agit d'un no-go en Java, mais semble être OK avec la communauté C++. Cependant, je m'inquiète un peu des cas où une copie explicite ou une référence en lecture seule (const) à un objet devrait être retournée, est-ce exagéré?

2. Utilisez des méthodes get/set de style Java

Je ne sais pas si c'est de Java du tout, mais je veux dire ceci:

int getAmount(); // Returns the amount
void setAmount(int amount); // Sets the amount

. Utilisez les méthodes d'objectif/get get-set de style C

C'est un peu bizarre, mais apparemment de plus en plus courant:

int amount(); // Returns the amount
void amount(int amount); // Sets the amount

Pour que cela fonctionne, vous devrez trouver un nom différent pour votre variable membre. Certaines personnes ajoutent un trait de soulignement, d'autres ajoutent "m_". Je n'aime pas non plus.

Quel style utilisez-vous et pourquoi?

71
Noarth

De mon point de vue, assis avec 4 millions de lignes de code C++ (et ce n'est qu'un projet) du point de vue de la maintenance, je dirais:

  • Il est correct de ne pas utiliser les getters/setters si les membres sont immuables (c'est-à-dire const) ou simples sans dépendances (comme une classe de points avec les membres X et Y).

  • Si le membre est private seulement, il est également possible de sauter les getters/setters. Je compte également les membres des classes internes pimpl - comme private si l'unité .cpp est petite.

  • Si le membre est public ou protected (protected est tout aussi mauvais que public) et non -const, non simple ou a des dépendances alors utilisez des getters/setters.

En tant que gars de maintenance, ma principale raison de vouloir avoir des getters/setters est parce qu'alors j'ai un endroit pour mettre des points d'arrêt/journalisation/autre chose.

Je préfère le style de l'alternative 2. car c'est plus consultable (un élément clé dans l'écriture de code maintenable).

2) est le meilleur OMI, car il rend vos intentions plus claires. set_amount(10) est plus significatif que amount(10), et comme un effet secondaire Nice permet à un membre nommé amount.

Les variables publiques sont généralement une mauvaise idée, car il n'y a pas d'encapsulation. Supposons que vous ayez besoin de mettre à jour un cache ou d'actualiser une fenêtre lorsqu'une variable est mise à jour? Dommage si vos variables sont publiques. Si vous avez une méthode définie, vous pouvez l'ajouter ici.

7
AshleysBrain
  1. Je n'utilise jamais ce style. Parce que cela peut limiter l'avenir de la conception de votre classe et les geters ou setters explicites sont tout aussi efficaces avec de bons compilateurs.

    Bien sûr, en réalité, les getters ou setters explicites en ligne créent autant de dépendance sous-jacente à l'implémentation de la classe. Ils réduisent simplement la dépendance sémantique. Vous devez toujours tout recompiler si vous les modifiez.

  2. C'est mon style par défaut lorsque j'utilise des méthodes d'accesseur.

  3. Ce style me semble trop "intelligent". Je l'utilise à de rares occasions, mais uniquement dans les cas où je veux vraiment que l'accesseur se sente autant que possible comme une variable.

Je pense qu'il y a un cas pour de simples sacs de variables avec éventuellement un constructeur pour s'assurer qu'elles sont toutes initialisées à quelque chose de sain. Lorsque je fais cela, je le transforme simplement en struct et le laisse tout public.

7
Omnifarious
  1. C'est un bon style si nous voulons simplement représenter les données pure.

  2. Je n'aime pas ça :) parce que get_/set_ est vraiment inutile quand on peut les surcharger en C++.

  3. STL utilise ce style, tel que std::streamString::str et std::ios_base::flags, sauf quand il faut l'éviter! quand? Lorsque le nom de la méthode entre en conflit avec le nom d'un autre type, alors get_/set_ le style est utilisé, tel que std::string::get_allocator à cause de std::allocator.

6
M. Sadeq H. E.

En général, je pense que ce n'est pas une bonne idée d'avoir trop de getters et setters utilisés par trop d'entités dans le système. C'est juste une indication d'une mauvaise conception ou d'une mauvaise encapsulation.

Cela dit, si une telle conception doit être refactorisée et que le code source est disponible, je préférerais utiliser le modèle Visitor Design. La raison est:

une. Il donne à une classe l'occasion de décider qui autoriser l'accès à son état privé

b. Il donne à une classe l'occasion de décider quel accès autoriser à chacune des entités intéressées par son état privé

c. Il documente clairement cet accès externe via une interface de classe claire

L'idée de base est:

a) Refonte si possible autrement,

b) Refactoriser de telle sorte que

  1. Tout accès à l'état de classe se fait via une interface bien connue individualiste

  2. Il devrait être possible de configurer une sorte de choses à faire et à ne pas faire pour chacune de ces interfaces, par ex. tous les accès depuis une entité externe BON doivent être autorisés, tous les accès depuis une entité externe MAUVAIS devrait être interdit, et l'entité externe OK devrait être autorisée à obtenir mais non définie (par exemple)

4
Chubsdad
  1. Je n'exclurais pas les accesseurs de l'utilisation. Peut pour certaines structures POD, mais je les considère comme une bonne chose (certains accesseurs peuvent également avoir une logique supplémentaire).

  2. Cela n'a pas vraiment d'importance la convention de nommage, si vous êtes cohérent dans votre code. Si vous utilisez plusieurs bibliothèques tierces, elles peuvent de toute façon utiliser des conventions de dénomination différentes. C'est donc une question de goût.

2
Cătălin Pitiș

J'ai vu l'idéalisation de classes au lieu de types intégraux pour faire référence à des données significatives.

Quelque chose comme ça ci-dessous ne fait généralement pas bon usage des propriétés C++:

struct particle {
    float mass;
    float acceleration;
    float velocity;
} p;

Pourquoi? Parce que le résultat de p.mass * p.acceleration est un flottant et ne force pas comme prévu.

La définition de classes pour désigner un but (même si c'est une valeur, comme montant mentionné plus haut) a plus de sens et nous permet de faire quelque chose comme:

struct amount
{
    int value;

    amount() : value( 0 ) {}
    amount( int value0 ) : value( value0 ) {}
    operator int()& { return value; }
    operator int()const& { return value; }
    amount& operator = ( int const newvalue )
    {
        value = newvalue;
        return *this;
    }
};

Vous pouvez accéder implicitement à la valeur en montant par l'opérateur int. En outre:

struct wage
{
    amount balance;

    operator amount()& { return balance; }
    operator amount()const& { return balance; }
    wage& operator = ( amount const&  newbalance )
    {
        balance = newbalance;
        return *this;
    }
};

Utilisation de Getter/Setter:

void wage_test()
{
    wage worker;
    (amount&)worker = 100; // if you like this, can remove = operator
    worker = amount(105);  // an alternative if the first one is too weird
    int value = (amount)worker; // getting amount is more clear
}

Il s'agit d'une approche différente, cela ne signifie pas qu'elle est bonne ou mauvaise, mais différente.

1
gus

Une possibilité supplémentaire pourrait être:

int& amount();

Je ne suis pas sûr de le recommander, mais cela a l'avantage que la notation inhabituelle peut empêcher les utilisateurs de modifier les données.

str.length() = 5; // Ok string is a very bad example :)

Parfois, c'est peut-être juste le bon choix:

image(point) = 255;  

Encore une autre possibilité, utilisez la notation fonctionnelle pour modifier l'objet.

edit::change_amount(obj, val)

De cette façon, la fonction dangereuse/d'édition peut être retirée dans un espace de noms séparé avec sa propre documentation. Celui-ci semble venir naturellement avec une programmation générique.

0
log0

Permettez-moi de vous parler d'une possibilité supplémentaire, qui semble la plus consciente.

Besoin de lire et de modifier

Déclarez simplement cette variable publique:

class Worker {
public:
    int wage = 5000;
}

worker.wage = 8000;
cout << worker.wage << endl;

Besoin juste de lire

class Worker {
    int _wage = 5000;
public:
    inline int wage() {
        return _wage;
    }
}

worker.wage = 8000; // error !!
cout << worker.wage() << endl;

L'inconvénient de cette approche est que vous devez modifier tout le code appelant (ajouter des parenthèses, c'est-à-dire) lorsque vous souhaitez modifier le modèle d'accès.

0
Rok Kralj