Les classes imbriquées sont comme les classes normales, mais:
- ils ont une restriction d'accès supplémentaire (comme toutes les définitions à l'intérieur d'une définition de classe),
- ils ne polluent pas l'espace de noms donné , par exemple l'espace de noms global. Si vous pensez que la classe B est si profondément connectée à la classe A, mais que les objets de A et B ne sont pas nécessairement liés, alors vous voudrez peut-être que la classe B ne soit accessible que via l'étendue de la classe A (elle serait appelée A ::Classe).
Quelques exemples:
Classe d'imbrication publique pour la placer dans une portée de classe pertinente
Supposons que vous souhaitiez avoir une classe SomeSpecificCollection
qui regrouperait des objets de classe Element
. Vous pouvez alors soit:
déclarer deux classes: SomeSpecificCollection
et Element
- mauvais, car le nom "Element" est assez général pour provoquer un éventuel conflit de nom
introduire un espace de noms someSpecificCollection
et déclarer les classes someSpecificCollection::Collection
et someSpecificCollection::Element
. Pas de risque de clash de nom, mais peut-il devenir plus bavard?
déclarer deux classes globales SomeSpecificCollection
et SomeSpecificCollectionElement
- ce qui a des inconvénients mineurs, mais c'est probablement OK.
déclarer la classe globale SomeSpecificCollection
et la classe Element
comme sa classe imbriquée. Ensuite:
- vous ne risquez aucun conflit de nom car Element n'est pas dans l'espace de noms global,
- dans la mise en œuvre de
SomeSpecificCollection
vous faites référence à juste Element
, et partout ailleurs comme SomeSpecificCollection::Element
- ce qui ressemble + - à la même chose que 3., mais plus clair
- il est clair que c'est "un élément d'une collection spécifique", pas "un élément spécifique d'une collection"
- il est visible que
SomeSpecificCollection
c'est aussi une classe.
À mon avis, la dernière variante est certainement la conception la plus intuitive et donc la meilleure.
Permettez-moi de souligner - Ce n'est pas une grande différence de créer deux classes globales avec des noms plus verbeux. C'est juste un tout petit détail, mais à mon avis, cela rend le code plus clair.
Présentation d'une autre portée dans une portée de classe
Ceci est particulièrement utile pour introduire des typedefs ou des enums. Je vais juste poster un exemple de code ici:
class Product {
public:
enum ProductType {
FANCY, AWESOME, USEFUL
};
enum ProductBoxType {
BOX, BAG, CRATE
};
Product(ProductType t, ProductBoxType b, String name);
// the rest of the class: fields, methods
};
On appellera alors:
Product p(Product::FANCY, Product::BOX);
Mais quand on regarde les propositions de complétion de code pour Product::
, on obtiendra souvent toutes les valeurs d'énumération possibles (BOX, FANCY, CRATE) et il est facile de faire une erreur ici (les énumérations fortement typées de C ++ 0x résolvent en quelque sorte cela, mais tant pis ).
Mais si vous introduisez une portée supplémentaire pour ces énumérations en utilisant des classes imbriquées, les choses pourraient ressembler à ceci:
class Product {
public:
struct ProductType {
enum Enum { FANCY, AWESOME, USEFUL };
};
struct ProductBoxType {
enum Enum { BOX, BAG, CRATE };
};
Product(ProductType::Enum t, ProductBoxType::Enum b, String name);
// the rest of the class: fields, methods
};
Ensuite, l'appel ressemble à:
Product p(Product::ProductType::FANCY, Product::ProductBoxType::BOX);
Ensuite, en tapant Product::ProductType::
un IDE, on obtiendra uniquement les énumérations de la portée souhaitée suggérée. Cela réduit également le risque de faire une erreur.
Bien sûr, cela n'est peut-être pas nécessaire pour les petites classes, mais si l'on a beaucoup d'énumérations, cela facilite les choses pour les programmeurs clients.
De la même manière, vous pouvez «organiser» un grand nombre de typedefs dans un modèle, si jamais vous en aviez besoin. C'est parfois un modèle utile.
L'idiome PIMPL
Le PIMPL (abréviation de Pointer to IMPLementation) est un idiome utile pour supprimer les détails d'implémentation d'une classe de l'en-tête. Cela réduit le besoin de recompiler les classes en fonction de l'en-tête de la classe chaque fois que la partie "implémentation" de l'en-tête change.
Il est généralement implémenté à l'aide d'une classe imbriquée:
Xh:
class X {
public:
X();
virtual ~X();
void publicInterface();
void publicInterface2();
private:
struct Impl;
std::unique_ptr<Impl> impl;
}
X.cpp:
#include "X.h"
#include <windows.h>
struct X::Impl {
HWND hWnd; // this field is a part of the class, but no need to include windows.h in header
// all private fields, methods go here
void privateMethod(HWND wnd);
void privateMethod();
};
X::X() : impl(new Impl()) {
// ...
}
// and the rest of definitions go here
Ceci est particulièrement utile si la définition de classe complète a besoin de la définition de types à partir d'une bibliothèque externe qui a un fichier d'en-tête lourd ou juste moche (prenez WinAPI). Si vous utilisez PIMPL, vous pouvez inclure une fonctionnalité spécifique à WinAPI uniquement dans .cpp
et ne jamais l'inclure dans .h
.