为什么会在C ++中使用嵌套类?
有人可以指点我的理解和使用嵌套类的一些不错的资源? 我有一些像编程原则这样的材料,像这样的IBM知识中心 – 嵌套类
但是我仍然无法理解他们的目的。 有人可以帮我吗?
嵌套的类很酷,用于隐藏实现细节
列表:
class List { public: List(): head(NULL), tail(NULL) {} private: class Node { public: int data; Node* next; Node* prev; }; private: Node* head; Node* tail; };
在这里,我不想暴露Node,因为其他人可能决定使用这个类,这会阻止我更新我的类,因为暴露的东西是公共API的一部分,必须永久保存 。 通过使课程变得私密,我不但隐藏了实现,而且还说这是我的,我可以随时更改它,所以你不能使用它。
看看std::list
或std::map
他们都包含隐藏的类(或者他们?)。 关键是他们可能会也可能不会,但是因为这个实现是隐藏的,所以STL的构build者能够更新代码,而不会影响你如何使用代码,或者在STL周围留下很多旧的行李,因为他们需要以保持与一些愚蠢的后向兼容性谁决定他们想要使用隐藏在list
内的节点类。
嵌套类就像常规类,但是:
- 他们有额外的访问限制(因为类定义内的所有定义)
- 他们不污染给定的命名空间 ,例如全局命名空间。 如果你觉得B类与A类有如此深的联系,但是A和B的对象不一定是相关的,那么你可能希望B类只能通过范围A类来访问(它将被称为A ::类)。
一些例子:
公共嵌套类将其放入相关类的范围
假设你想要一个SomeSpecificCollection
类来聚合Element
类的对象。 那么你可以:
-
声明两个类:
SomeSpecificCollection
和Element
– 不好,因为名称“元素”是足够一般的,以便导致可能的名称冲突 -
引入一个命名空间
someSpecificCollection
并声明类someSpecificCollection::Collection
和someSpecificCollection::Element
。 没有名称冲突的风险,但可以得到更详细的? -
声明两个全局类
SomeSpecificCollection
和SomeSpecificCollectionElement
– 它有小缺点,但可能是好的。 -
声明全局类
SomeSpecificCollection
和类Element
作为它的嵌套类。 然后:- 因为Element不在全局命名空间中,所以不会冒任何名称类,
- 在
SomeSpecificCollection
实现中,你只能引用Element
,而在其他地方就像SomeSpecificCollection::Element
– 看起来+ – 与3.相同,但是更清晰 - 它变得简单,它是“特定集合的元素”,而不是“集合的特定元素”
-
SomeSpecificCollection
也是一个类。
在我看来,最后一个变种绝对是最直观的,因此也是最好的devise。
让我强调一下 – 使两个全球化的名字更加冗长,这并没有太大的区别。 它只是一个小小的细节,但是它使代码更清晰。
在类范围内引入另一个范围
这对于引入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::
代码完成build议时,通常会列出所有可能的枚举值(BOX,FANCY,CRATE),并且在这里很容易犯一个错误(C ++ 0x的强types枚举types可以解决这个问题,但是不要紧)。
但是,如果您使用嵌套类为这些枚举引入了额外的范围,则可能如下所示:
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);
然后,通过在IDE中inputProduct::ProductType::
这也降低了犯错的风险。
当然,这对于小类来说可能不是必需的,但是如果有一个枚举types很多的话,那么对于客户程序员来说就更容易了。
同样的,如果你有需要的话,你可以在模板中“组织”一大堆types定义。 有时候这是一个有用的模式。
PIMPL的成语
PIMPL(Private IMPLementation的简称)是一种用于从头中删除类的实现细节的习惯用法。 这减less了重新编译类的需要,只要标题的“实现”部分改变,这取决于类的头部。
它通常使用嵌套类来实现:
XH:
class X { public: X(); virtual ~X(); void publicInterface(); void publicInterface2(); private: struct Impl; std::unique_ptr<Impl> impl; }
X.cpp:
#include "Xh" #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特定的function仅封装在.cpp
并且永远不会将其包含在.h
。
我不使用嵌套类,但我现在使用它们。 特别是当我定义某种数据types,然后我想定义一个为该数据typesdevise的STL函子。
例如,考虑具有ID号,types代码和字段名称的genericsField
类。 如果我想通过ID号或名称search这些Field
的vector
,我可能会构造一个函数来实现:
class Field { public: unsigned id_; string name_; unsigned type_; class match : public std::unary_function<bool, Field> { public: match(const string& name) : name_(name), has_name_(true) {}; match(unsigned id) : id_(id), has_id_(true) {}; bool operator()(const Field& rhs) const { bool ret = true; if( ret && has_id_ ) ret = id_ == rhs.id_; if( ret && has_name_ ) ret = name_ == rhs.name_; return ret; }; private: unsigned id_; bool has_id_; string name_; bool has_name_; }; };
然后,需要search这些Field
的代码可以使用Field
类本身内的match
范围:
vector<Field>::const_iterator it = find_if(fields.begin(), fields.end(), Field::match("FieldName"));
我们可以使用嵌套类实现Builder模式 。 特别是在C ++中,我个人觉得它在语义上更干净。 例如:
class Product{ public: class Builder; } class Product::Builder { // Builder Implementation }
而不是:
class Product {} class ProductBuilder {}