web-dev-qa-db-fra.com

Structures C ++ avec fonctions membres et classes avec variables publiques

C'est vraiment une question de bonne forme/meilleures pratiques. J'utilise des structures en C++ pour former des objets qui sont essentiellement conçus pour contenir des données, plutôt que de créer une classe avec une tonne de méthodes d'accesseur qui ne font que récupérer/définir les valeurs. Par exemple:

struct Person {
    std::string name;
    DateObject dob;
    (...)
};

Si vous imaginez 20 variables supplémentaires, écrire ceci comme une classe avec des membres privés et des accesseurs d'une quarantaine d'années est une douleur à gérer et me semble un gaspillage.

Parfois, cependant, je devrais également ajouter une sorte de fonctionnalité minimale aux données. Dans l'exemple, disons que j'ai aussi parfois besoin de l'âge, en fonction de la date de naissance:

struct Person {
    std::string name;
    DateObject dob;
    (...)
    int age() {return calculated age from dob;}
}

Bien sûr, pour toute fonctionnalité complexe, je ferais une classe, mais pour une simple fonctionnalité comme celle-ci, est-ce une "mauvaise conception"? Si j'utilise une classe, est-ce une mauvaise forme de conserver les variables de données en tant que membres de la classe publique, ou dois-je simplement l'accepter et créer des classes avec un tas de méthodes d'accesseur? Je comprends les différences entre les classes et les structures, je pose simplement des questions sur les meilleures pratiques.

26
amnesia

Je pense qu'il y a deux principes de conception importants à considérer ici:

  1. Masquer la représentation d'une classe via une interface s'il y a un invariant sur cette classe.

    Une classe a un invariant lorsqu'il existe un état non valide pour cette classe. La classe doit maintenir son invariant à tout moment.

    Considérons un type Point qui représente un point géométrique 2D. Il doit s'agir simplement d'un struct avec des membres de données publics x et y. Il n'y a pas de point invalide. Chaque combinaison de valeurs x et y est parfaitement correcte.

    Dans le cas d'un Person, le fait qu'il ait des invariants dépend entièrement du problème en question. Considérez-vous des choses comme un nom vide comme un nom valide? Le Person peut-il avoir une date de naissance quelconque? Pour votre cas, je pense que la réponse est oui et votre classe devrait garder les membres publics.

    Voir: les classes doivent appliquer les invariants

  2. Les fonctions non membres non-amis améliorent l'encapsulation.

    Il n'y a aucune raison pour que votre fonction age soit implémentée en tant que fonction membre. Le résultat de age peut être calculé à l'aide de l'interface publique de Person, il n'a donc aucune raison d'être une fonction membre. Placez-le dans le même espace de noms que Person afin qu'il soit trouvé par la recherche dépendante de l'argument. Les fonctions trouvées par ADL font partie de l'interface de cette classe; ils n'ont tout simplement pas accès aux données privées.

    Si vous en faisiez une fonction membre et introduisiez un jour un état privé dans Person, vous auriez une dépendance inutile. Soudain, age a plus d'accès aux données qu'il n'en a besoin.

    Voir: Comment les fonctions non membres améliorent l'encapsulation

Voici donc comment je le mettrais en œuvre:

struct Person {
  std::string name;
  DateObject dob;
};

int age(const Person& person) {
  return calculated age from person.dob;
}
23
Joseph Mansfield

En C++, les Structs sont des classes, avec la différence seulement (à laquelle je pense, au moins) étant que dans Structs, les membres sont publics par défaut, mais dans les cours, ils sont privés. Cela signifie qu'il est parfaitement acceptable d'utiliser Structs tel que vous êtes - cet article l'explique bien.

6
Polar

En C++, la seule différence entre les structures et les classes est que les structures sont publiquement visibles par défaut. Une bonne directive consiste à utiliser des structures en tant que données anciennes (POD) qui contiennent uniquement des données et n'utilisent des classes que lorsque davantage de fonctionnalités (fonctions membres) sont requises.

Vous vous demandez peut-être toujours s'il faut simplement avoir des variables publiques dans la classe ou utiliser des fonctions membres; considérez le scénario suivant.

Disons que vous avez une classe A qui a une fonction GetSomeVariable qui est simplement un getter pour une variable privée:

class A
{
    double _someVariable;

public:
    double GetSomeVariable() { return _someVariable; }
};

Et si, vingt ans plus tard, la signification de cette variable change et que vous devez, disons, la multiplier par 0,5? Lorsque vous utilisez un getter, c'est simple; il suffit de renvoyer la variable multipliée par 0,5:

    double GetSomeVariable() { return 0.5*_someVariable; }

En faisant cela, vous autorisez une maintenabilité facile et permettez une modification facile.

1
Mohammed Hossain

Si vous voulez un détenteur de données, préférez struct sans aucune méthode get/set.

S'il y en a plus, comme dans ce cas "Personne".

  1. Il modélise une entité du monde réel,
  2. A un état et un comportement définis,
  3. Interagit avec le monde extérieur,
  4. Présente une relation simple/complexe avec d'autres entités,
  5. il peut évoluer au fil du temps,

alors c'est un candidat parfait pour une classe.

1
Arun

"Utilisez une structure uniquement pour les objets passifs qui transportent des données; tout le reste est une classe."

dites google guidlines , je le fais de cette façon et je trouve que c'est une bonne règle. En plus de cela, je pense que vous pouvez définir votre propre pragmatique, ou dévier de cette règle si cela a vraiment du sens.

0
citykid

Je ne veux pas déclencher une guerre sainte ici; Je le différencie généralement de cette manière:

  • Pour les objets POD (c'est-à-dire, uniquement pour les données, sans comportement exposé), déclarez les composants internes publics et accédez-y directement. L'utilisation du mot clé struct est pratique ici et sert également d'indicateur de l'utilisation de l'objet.
  • Pour les objets non-POD, déclarez les internes privés et définissez les getters/setters publics. L'utilisation du mot clé class est plus naturelle dans ces cas.
0
SomeWittyUsername