Verschachtelte Klassen sind wie normale Klassen, aber:
- Sie haben zusätzliche Zugriffsbeschränkungen (wie alle Definitionen innerhalb einer Klassendefinition).
- Sie verschmutzen den angegebenen Namespace nicht , z. B. den globalen Namespace. Wenn Sie der Meinung sind, dass Klasse B so eng mit Klasse A verbunden ist, die Objekte von A und B jedoch nicht unbedingt miteinander verbunden sind, möchten Sie möglicherweise, dass auf die Klasse B nur über das Scoping der A-Klasse zugegriffen werden kann (dies wird als A bezeichnet) ::Klasse).
Einige Beispiele:
Klasse öffentlich verschachteln, um sie in einen Bereich der relevanten Klasse zu setzen
Angenommen, Sie möchten eine Klasse haben, die Klassenobjekte SomeSpecificCollection
aggregiert Element
. Sie können dann entweder:
Deklarieren Sie zwei Klassen: SomeSpecificCollection
und Element
- schlecht, da der Name "Element" allgemein genug ist, um einen möglichen Namenskonflikt zu verursachen
Führen Sie einen Namespace ein someSpecificCollection
und deklarieren Sie Klassen someSpecificCollection::Collection
und someSpecificCollection::Element
. Kein Risiko eines Namenskonflikts, aber kann es noch ausführlicher werden?
deklarieren Sie zwei globale Klassen SomeSpecificCollection
und SomeSpecificCollectionElement
- was kleinere Nachteile hat, aber wahrscheinlich in Ordnung ist.
Deklarieren Sie globale Klasse SomeSpecificCollection
und Klasse Element
als verschachtelte Klasse. Dann:
- Sie riskieren keine Namenskonflikte, da sich Element nicht im globalen Namespace befindet.
- In der Implementierung von
SomeSpecificCollection
beziehen Sie sich auf gerecht Element
und überall sonst als SomeSpecificCollection::Element
- was + aussieht - das gleiche wie 3., aber klarer
- Es wird ganz einfach, dass es "ein Element einer bestimmten Sammlung" ist, nicht "ein bestimmtes Element einer Sammlung".
- Es ist sichtbar, dass dies
SomeSpecificCollection
auch eine Klasse ist.
Meiner Meinung nach ist die letzte Variante definitiv das intuitivste und damit beste Design.
Lassen Sie mich betonen - Es ist kein großer Unterschied, zwei globale Klassen mit ausführlicheren Namen zu erstellen. Es ist nur ein kleines Detail, aber imho macht es den Code klarer.
Einführung eines anderen Bereichs innerhalb eines Klassenbereichs
Dies ist besonders nützlich für die Einführung von Typedefs oder Enums. Ich werde hier nur ein Codebeispiel posten:
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
};
Man wird dann anrufen:
Product p(Product::FANCY, Product::BOX);
Wenn man sich jedoch Vorschläge zur Code-Vervollständigung ansieht Product::
, werden oft alle möglichen Aufzählungswerte (BOX, FANCY, CRATE) aufgelistet, und es ist leicht, hier einen Fehler zu machen (die stark typisierten Aufzählungen von C ++ 0x lösen das irgendwie, aber egal ).
Wenn Sie jedoch mithilfe verschachtelter Klassen zusätzlichen Bereich für diese Aufzählungen einführen, könnte dies folgendermaßen aussehen:
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
};
Dann sieht der Anruf so aus:
Product p(Product::ProductType::FANCY, Product::ProductBoxType::BOX);
Product::ProductType::
Wenn Sie dann eine IDE eingeben , erhalten Sie nur die Aufzählungen aus dem gewünschten vorgeschlagenen Bereich. Dies verringert auch das Fehlerrisiko.
Natürlich wird dies für kleine Klassen möglicherweise nicht benötigt, aber wenn man viele Aufzählungen hat, erleichtert dies den Client-Programmierern die Arbeit.
Auf die gleiche Weise könnten Sie eine große Anzahl von Typedefs in einer Vorlage "organisieren", falls Sie dies jemals benötigen würden. Es ist manchmal ein nützliches Muster.
Die PIMPL-Sprache
Die PIMPL (kurz für Pointer to IMPLementation) ist eine nützliche Redewendung, um die Implementierungsdetails einer Klasse aus dem Header zu entfernen. Dies reduziert die Notwendigkeit, Klassen abhängig vom Header der Klasse neu zu kompilieren, wenn sich der "Implementierung" -Teil des Headers ändert.
Es wird normalerweise mit einer verschachtelten Klasse implementiert:
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
Dies ist besonders nützlich, wenn für die vollständige Klassendefinition die Definition von Typen aus einer externen Bibliothek erforderlich ist, die eine schwere oder nur hässliche Header-Datei enthält (nehmen Sie WinAPI). Wenn Sie PIMPL verwenden, können Sie WinAPI-spezifische Funktionen nur in einschließen .cpp
und niemals einschließen .h
.