Вложенные классы похожи на обычные классы, но:
- они имеют дополнительное ограничение доступа (как и все определения внутри определения класса),
- они не загрязняют данное пространство имен , например, глобальное пространство имен. Если вы чувствуете, что класс B так глубоко связан с классом A, но объекты A и B не обязательно связаны, то вы можете захотеть, чтобы класс B был доступен только через область видимости класса A (он будет называться A ::Класс).
Некоторые примеры:
Публично вложенный класс, чтобы поместить его в область видимости соответствующего класса
Предположим, вы хотите иметь класс, SomeSpecificCollection
который будет агрегировать объекты класса Element
. Вы можете затем либо:
объявлять два класса: SomeSpecificCollection
и Element
- плохо, потому что имя «Элемент» достаточно общее, чтобы вызвать возможное столкновение имен
ввести пространство имен someSpecificCollection
и объявить классы someSpecificCollection::Collection
и someSpecificCollection::Element
. Нет риска столкновения имен, но может ли оно стать более подробным?
объявить два глобальных класса SomeSpecificCollection
и SomeSpecificCollectionElement
- которые имеют незначительные недостатки, но, вероятно, в порядке.
объявить глобальный класс SomeSpecificCollection
и класс Element
как его вложенный класс. Затем:
- вы не рискуете столкновением имен, так как Элемент не находится в глобальном пространстве имен,
- в реализации
SomeSpecificCollection
вы ссылаетесь просто Element
и везде SomeSpecificCollection::Element
- как - + выглядит так же, как 3., но более понятно
- становится просто, что это «элемент определенной коллекции», а не «конкретный элемент коллекции»
- Видно, что
SomeSpecificCollection
это тоже класс.
На мой взгляд, последний вариант, безусловно, самый интуитивный и, следовательно, лучший дизайн.
Позвольте мне подчеркнуть - это не большая разница от создания двух глобальных классов с более подробными именами. Это просто крошечная деталь, но imho делает код более понятным.
Введение другой области видимости внутри класса
Это особенно полезно для введения typedefs или перечислений. Я просто выложу пример кода здесь:
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
};
Один тогда позвонит:
Product p(Product::FANCY, Product::BOX);
Но при рассмотрении предложений по дополнению кода Product::
часто можно получить список всех возможных значений перечисления (BOX, FANCY, CRATE), и здесь легко ошибиться (строго типизированные перечисления C ++ 0x решают эту проблему, но не берите в голову) ).
Но если вы введете дополнительную область видимости для этих перечислений, используя вложенные классы, вещи могут выглядеть следующим образом:
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
};
Тогда звонок выглядит так:
Product p(Product::ProductType::FANCY, Product::ProductBoxType::BOX);
Затем, введя Product::ProductType::
IDE, вы получите только перечисления из предложенной области действия. Это также снижает риск ошибки.
Конечно, это может не понадобиться для небольших классов, но если у вас много перечислений, то это облегчает работу клиентских программистов.
Таким же образом, вы можете «организовать» большую группу typedef в шаблоне, если вам когда-нибудь понадобится. Это полезный шаблон иногда.
Идиома PIMPL
PIMPL (сокращение от Pointer to IMPLementation) - это идиома, полезная для удаления деталей реализации класса из заголовка. Это уменьшает необходимость перекомпиляции классов в зависимости от заголовка класса всякий раз, когда изменяется часть «реализации» заголовка.
Обычно это реализуется с использованием вложенного класса:
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
Это особенно полезно, если полное определение класса требует определения типов из некоторой внешней библиотеки, которая имеет тяжелый или просто уродливый заголовочный файл (например, WinAPI). Если вы используете PIMPL, тогда вы можете заключать любые специфичные для WinAPI функции только в .cpp
и никогда не включать их в .h
.