为什么要在C ++中使用嵌套类?

时间:2010-12-31 17:14:59

标签: c++ nested inner-classes

有人可以指点我一些很好的资源来理解和使用嵌套类吗?我有一些像编程原则和类似IBM Knowledge Center - Nested Classes

之类的材料

但我仍然无法理解他们的目的。有人可以帮助我吗?

6 个答案:

答案 0 :(得分:206)

嵌套类很容易隐藏实现细节。

列表:

class List
{
    public:
        List(): head(nullptr), tail(nullptr) {}
    private:
        class Node
        {
              public:
                  int   data;
                  Node* next;
                  Node* prev;
        };
    private:
        Node*     head;
        Node*     tail;
};

这里我不想公开Node,因为其他人可能决定使用该类,这会阻碍我更新我的类,因为任何公开的API都是公共API的一部分,必须保持永远。通过使类私有,我不仅隐藏了实现,我也说这是我的,我可以随时更改它,所以你不能使用它。

查看std::liststd::map它们都包含隐藏的类(或者它们是什么?)。重点是它们可能会也可能不会,但是因为实现是私有的并且隐藏了STL的构建者能够在不影响您使用代码的方式的情况下更新代码,或者因为需要而在STL周围留下大量旧行李保持与某些傻瓜的向后兼容性,他们决定使用隐藏在list内的Node类。

答案 1 :(得分:136)

嵌套类就像常规类一样,但是:

  • 他们有额外的访问限制(因为类定义中的所有定义都有),
  • 他们不污染给定的命名空间,例如全局命名空间如果你觉得B类与A类有如此深的联系,但是A和B的对象不一定相关,那么你可能希望只能通过对A类进行范围化来访问B类(它将被称为A ::类)。

一些例子:

公开嵌套类以将其置于相关类

的范围内

假设您希望有一个类SomeSpecificCollection来聚合类Element的对象。然后你可以:

  1. 声明两个类:SomeSpecificCollectionElement - 不好,因为名称“Element”足够通用以引起可能的名称冲突

  2. 引入名称空间someSpecificCollection并声明类someSpecificCollection::CollectionsomeSpecificCollection::Element。没有名字冲突的风险,但是它会变得更加冗长吗?

  3. 声明两个全局类SomeSpecificCollectionSomeSpecificCollectionElement - 这有一些小缺点,但可能没问题。

  4. 将全局类SomeSpecificCollection和类Element声明为其嵌套类。然后:

    • 您不会冒任何名称冲突的风险,因为Element不在全局命名空间中,
    • SomeSpecificCollection的实施中,您只引用Element,其他地方仅引用SomeSpecificCollection::Element - 其外观+ - 与3.相同,但更清晰
    • 很简单,它是“特定集合的元素”,而不是“集合中的特定元素”
    • 可见SomeSpecificCollection也是一个类。
  5. 在我看来,最后一个版本绝对是最直观,最好的设计。

    让我强调一下 - 与制作两个名字更详细的全局类并没有太大区别。它只是一个小细节,但它使代码更清晰。

    在类范围内引入另一个范围


    这对于引入typedef或枚举特别有用。我将在这里发布一个代码示例:

    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);
    

    然后在IDE中键入Product::ProductType::,只会得到建议所需范围的枚举。这也降低了犯错的风险。

    当然,小类可能不需要这样,但如果有很多枚举,那么它会让客户端程序员更容易。

    同样,如果您有需要,可以在模板中“组织”一大堆typedef。这有时是一种有用的模式。

    PIMPL习语


    PIMPL(指向IMPLementation的指针的缩写)是一个习惯用法,用于从标题中删除类的实现细节。每当标题的“实现”部分发生变化时,这就减少了重新编译类的需要,具体取决于类的标题。

    它通常使用嵌套类实现:

    X.h:

    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,则只能在.cpp中包含任何特定于WinAPI的功能,并且不得将其包含在.h中。

答案 2 :(得分:21)

我不太多使用嵌套类,但我偶尔会使用它们。特别是当我定义某种数据类型时,我想要定义一个为该数据类型设计的STL仿函数。

例如,考虑具有ID号,类型代码和字段名称的通用Field类。如果我想通过ID号或名称搜索这些vector的{​​{1}},我可以构建一个仿函数来执行此操作:

Field

然后,需要搜索这些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_; }; }; 的代码可以使用Field类本身范围内的match

Field

答案 3 :(得分:12)

One can implement a Builder pattern with nested class。特别是在C ++中,我个人认为它在语义上更清晰。例如:

class Product{
    public:
        class Builder;
}
class Product::Builder {
    // Builder Implementation
}

而不是:

class Product {}
class ProductBuilder {}

答案 4 :(得分:0)

我认为使类嵌套而不仅仅是朋友类的主要目的是能够在派生类中继承嵌套类。 C++中没有继承友谊。

答案 5 :(得分:0)

您还可以考虑主函数的一流 ass 类型,您可以在其中启动所有需要的类来一起工作。例如类 Game,启动所有其他类,如窗口、英雄、敌人、级别等。这样你就可以摆脱主函数中的所有东西了。您可以在哪里创建 Game 对象,也可以进行一些与 Gemente 本身无关的额外外部调用。