web-dev-qa-db-fra.com

Avantages et inconvénients de l'utilisation de classes et d'énumérations imbriquées C++?

Quels sont les avantages et les inconvénients de l'utilisation de classes et d'énumérations C++ publiques imbriquées? Par exemple, supposons que vous ayez une classe appelée printer et que cette classe stocke également des informations sur les bacs de sortie, vous pourriez avoir:

class printer
{
public:
    std::string name_;

    enum TYPE
    {
        TYPE_LOCAL,
        TYPE_NETWORK,
    };

    class output_tray
    {
        ...
    };
    ...
};

printer prn;
printer::TYPE type;
printer::output_tray tray;

Alternativement:

class printer
{
public:
    std::string name_;
    ...
};

enum PRINTER_TYPE
{
    PRINTER_TYPE_LOCAL,
    PRINTER_TYPE_NETWORK,
};

class output_tray
{
    ...
};

printer prn;
PRINTER_TYPE type;
output_tray tray;

Je peux voir les avantages de l'imbrication d'ens/classes privés, mais lorsqu'il s'agit d'environnements publics, le bureau est divisé - il semble être plus un choix de style.

Alors, que préférez-vous et pourquoi?

44
Rob

Classes imbriquées

Il y a plusieurs effets secondaires sur les classes imbriquées dans des classes que je considère généralement comme des défauts (si ce n’est pas des antipatterns purs).

Imaginons le code suivant:

class A
{
   public :
      class B { /* etc. */ } ;

   // etc.
} ;

Ou même:

class A
{
   public :
      class B ;

   // etc.
} ;

class A::B
{
   public :

   // etc.
} ;

Alors:

  • Accès privilégié: A :: B a un accès privilégié à tous les membres de A (méthodes, variables, symboles, etc.), ce qui affaiblit l'encapsulation.
  • La portée de A est candidate à la recherche de symbole: le code de l'intérieur de B verra tout symboles de A en tant que candidats possibles pour une recherche de symbole, ce qui peut confondre le code
  • déclaration en aval: Il n’existe aucun moyen de déclarer en aval A :: B sans donner une déclaration complète de A
  • Extensibilité: Il est impossible d'ajouter une autre classe A :: C à moins d'être propriétaire de A
  • Code verbosité: mettre des classes en classes ne fait que rendre les en-têtes plus grands. Vous pouvez toujours séparer cela en plusieurs déclarations, mais il n'y a aucun moyen d'utiliser des alias, des imports ou des usings similaires à des espaces de noms.

En conclusion, à moins d'exceptions (par exemple, la classe imbriquée est une partie intime de la classe d'imbrication ... Et même dans ce cas ...), je ne vois pas l'intérêt pour les classes imbriquées dans le code normal, car les défauts surpassent magnifiquement les avantages perçus .

En outre, cela ressemble à une tentative maladroite de simuler un espacement de noms sans utiliser d'espaces de noms C++.

Du côté des pros, vous isolez ce code, et s'il est privé, vous le rendez inutilisable, mais à partir de la classe "extérieur" ...

Enums imbriqués

Avantages: tout.

Con: Rien.

Le fait est que les items enum vont polluer la portée globale:

// collision
enum Value { empty = 7, undefined, defined } ;
enum Glass { empty = 42, half, full } ;

// empty is from Value or Glass?

En plaçant chaque énumération dans un espace de nom/classe différent, vous éviterez cette collision:

namespace Value { enum type { empty = 7, undefined, defined } ; }
namespace Glass { enum type { empty = 42, half, full } ; }

// Value::type e = Value::empty ;
// Glass::type f = Glass::empty ;

Notez que C++ 0x a défini l'énumération de classe:

enum class Value { empty, undefined, defined } ;
enum class Glass { empty, half, full } ;

// Value e = Value::empty ;
// Glass f = Glass::empty ;

exactement pour ce genre de problèmes.

44
paercebal

Un inconvénient qui peut devenir un gros problème pour les grands projets est qu’il est impossible de faire une déclaration directe pour les classes ou les énumérations imbriquées.

6
Adam Rosenfield

L'utilisation de classes C++ publiques imbriquées ne présente aucun avantage ni aucun inconvénient. Il n'y a que des faits. Ces faits sont prescrits par le standard C++. Qu'un fait sur les classes C++ publiques imbriquées soit un avantage ou un inconvénient dépend du problème particulier que vous essayez de résoudre. L'exemple que vous avez donné ne permet pas de déterminer si les classes imbriquées sont appropriées ou non.

Un fait sur les classes imbriquées est qu’elles ont un accès privilégié à tous les membres de la classe à laquelle elles appartiennent. C'est un inconvénient, si les classes imbriquées n'ont pas besoin d'un tel accès. Mais si la classe imbriquée n'a pas besoin d'un tel accès, elle n'aurait pas dû être déclarée en tant que classe imbriquée. Il existe des situations dans lesquelles une classeAveut accorder un accès privilégié à certaines autres classesB. Il y a trois solutions à ce problème

  1. FaireBun ami deA
  2. FaireBune classe imbriquée deA
  3. Créez les méthodes et les attributs, dontBa besoin, membres publics deA.

Dans cette situation, c'est le numéro 3 qui viole l'encapsulation, parce queAcontrôle ses amis et ses classes imbriquées, mais pas les classes qui appellent ses méthodes publiques ou accèdent à ses attributs publics.

Un autre fait concernant les classes imbriquées est qu’il est impossible d’ajouter une autre classe A :: C en tant que classe imbriquée deAsans être propriétaire deA. Cependant, cela est parfaitement raisonnable, car les classes imbriquées ont un accès privilégié. S'il était possible d'ajouter A :: C en tant que classe imbriquée deA, alors A :: C pourrait tromperAen accordant l'accès à des informations privilégiées; et que vous violeriez l’encapsulation. C'est fondamentalement la même chose que pour la déclaration friend: la déclaration friend ne vous accorde aucun privilège spécial, que votre ami se cache des autres; il permet à vos amis d'accéder aux informations que vous cachez à vos non-amis. En C++, appeler quelqu'un comme ami est un acte altruiste et non égoïste. Il en va de même pour permettre à une classe d'être une classe imbriquée.

Quelques autres faits sur les classes publiques imbriquées:

  • La portée de A est candidate à la recherche de symbole de B : si vous ne le souhaitez pas, définissezBun ami deAau lieu d'une classe imbriquée. Cependant, dans certains cas, vous voulez exactement ce type de recherche de symbole.
  • A :: B ne peuvent pas être déclarés en aval :Aet A :: B sont étroitement couplés. Pouvoir utiliser A :: B sans savoirAne ferait que masquer ce fait.

Pour résumer, si l’outil ne répond pas à vos besoins, ne le blâmez pas; vous blâmer d'avoir utilisé l'outil; d'autres pourraient avoir des problèmes différents, pour lesquels l'outil est parfait.

2
Oswald

Si vous n'utilisez jamais la classe dépendante pour autre chose que de travailler avec les implémentations de la classe indépendante, les classes imbriquées conviennent, à mon avis.

C'est quand vous voulez utiliser la classe "interne" comme un objet à part entière que les choses peuvent commencer à devenir un peu manky et que vous devez commencer à écrire des routines d'extraction/insertion. Pas une belle situation.

2
Paul Nathan

Il semble que vous devriez utiliser des espaces de noms plutôt que des classes pour regrouper les éléments liés les uns aux autres. Un inconvénient que j'ai pu constater en faisant des classes imbriquées est que vous vous retrouvez avec un très gros fichier source qui pourrait être difficile à assimiler lorsque vous recherchez une section.

2
carson

N'oubliez pas que vous pouvez toujours promouvoir une classe imbriquée vers une classe supérieure plus tard, mais que vous ne pourrez peut-être pas faire l'inverse sans détruire le code existant. Par conséquent, mon conseil serait d'en faire une classe imbriquée en premier, et si cela commence à devenir un problème, d'en faire une classe de niveau supérieur dans la prochaine version.

1
Eric G.

paercebal a dit tout ce que je dirais à propos des énumérations imbriquées.

Les classes imbriquées WRT, mon cas d'utilisation commun et presque unique pour elles est quand j'ai une classe qui manipule un type spécifique de ressource, et j'ai besoin d'une classe de données qui représente quelque chose de spécifique à cette ressource. Dans votre cas, output_tray est peut-être un bon exemple, mais je n'utilise généralement pas de classes imbriquées si la classe doit avoir des méthodes appelées de l'extérieur de la classe contenante ou s'il s'agit davantage d'une classe de données. De manière générale, je n'emboîte pas non plus les classes de données, à moins que la classe contenue ne soit jamais directement référencée en dehors de la classe contenue.

Ainsi, par exemple, si j'avais une classe printer_manipulator, elle pourrait avoir une classe contenue pour les erreurs de manipulation d'imprimante, mais l'imprimante elle-même serait une classe non contenue.

J'espère que cela t'aides. :)

1
Nick

Je suis d'accord avec les articles qui préconisent l'intégration de votre enum dans une classe, mais il y a des cas où il est plus logique de ne pas le faire (mais s'il vous plaît, mettez-le au moins dans un espace de noms). Si plusieurs classes utilisent une énumération définie dans une classe différente, ces classes dépendent directement de cette autre classe concrète (qui possède l'énum). Cela représente sûrement un défaut de conception puisque cette classe sera responsable de cette énumération ainsi que d’autres responsabilités.

Donc, oui, incorporer l'énumération dans une classe si un autre code utilise uniquement cette énumération pour se connecter directement à cette classe concrète. Sinon, trouvez un meilleur endroit pour conserver l'énumération, telle qu'un espace de noms.

0
Pat Notz

Je peux voir un con pour les classes imbriquées, que l'on peut mieux utiliser la programmation générique.

Si la petite classe est définie en dehors de la grande, vous pouvez transformer la grande classe en modèle de classe et utiliser n'importe quelle "petite" classe dont vous aurez éventuellement besoin avec la grande classe.

La programmation générique est un outil puissant et, IMHO, nous devrions en tenir compte lors du développement de programmes extensibles. Étrange, que personne n'a mentionné ce point.

0
Yaroslav Nikitenko

Si vous placez l'énum dans une classe ou un espace de noms, intellisense pourra vous guider lorsque vous essayez de vous rappeler les noms d'énum. Une petite chose à coup sûr, mais parfois les petites choses importent.

0
Mark Ransom

Visual Studio 2008 ne semble pas être en mesure de fournir intellisense pour les classes imbriquées. Je suis donc passé à l'idiome PIMPL dans la plupart des cas où j'avais une classe imbriquée. Je mets toujours des énumérations dans la classe si elles ne sont utilisées que par cette classe, ou en dehors de la classe dans le même espace de noms que la classe lorsque plusieurs classes utilisent l'enum.

0
Brian Stewart

Pour moi, l’un des gros inconvénients de l’extérieur est qu’il fait partie de l’espace de noms global. Si l'énumération ou la classe associée ne s'applique vraiment qu'à la classe dans laquelle elle se trouve, alors cela a du sens. Ainsi, dans le cas de l’imprimante, tout ce qui inclut l’imprimante sait qu’il doit avoir un accès complet à l’énumération PRINTER_TYPE, où elle n’a pas vraiment besoin de le savoir. Je ne peux pas dire que j'ai déjà utilisé une classe interne, mais pour une énumération, cela semble plus logique de la garder à l'intérieur. Comme l'a souligné une autre affiche, il est également judicieux d'utiliser des espaces de noms pour regrouper des éléments similaires, car l'encrassement de l'espace de noms global peut être une mauvaise chose. J'ai déjà travaillé sur des projets de grande envergure et le simple fait de mettre en place une liste complète automatique sur l'espace de noms global prend 20 minutes. À mon avis, les énumérations imbriquées et les classes/structures namespaced sont probablement l’approche la plus propre.

0
DavidG