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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4571355/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 15:47:54  来源:igfitidea点击:

Why would one use nested classes in C++?

c++nestedinner-classes

提问by bespectacled

Can someone please point me towards some nice resources for understanding and using nested classes? I have some material like Programming Principles and things like this IBM Knowledge Center - Nested Classes

有人可以指点我一些很好的资源来理解和使用嵌套类吗?我有一些诸如编程原则之类的材料以及诸如IBM 知识中心 - 嵌套类之类的东西

But I'm still having trouble understanding their purpose. Could someone please help me?

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

回答by Martin York

Nested classes are cool for hiding implementation details.

嵌套类非常适合隐藏实现细节。

List:

列表:

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

Here I don't want to expose Node as other people may decide to use the class and that would hinder me from updating my class as anything exposed is part of the public API and must be maintained forever. By making the class private, I not only hide the implementation I am also saying this is mine and I may change it at any time so you can not use it.

在这里,我不想公开 Node,因为其他人可能决定使用该类,这会妨碍我更新我的类,因为公开的任何内容都是公共 API 的一部分,必须永远维护。通过将类设为私有,我不仅隐藏了实现,而且还说这是我的,我可以随时更改它,因此您无法使用它。

Look at std::listor std::mapthey all contain hidden classes (or do they?). The point is they may or may not, but because the implementation is private and hidden the builders of the STL were able to update the code without affecting how you used the code, or leaving a lot of old baggage laying around the STL because they need to maintain backwards compatibility with some fool who decided they wanted to use the Node class that was hidden inside list.

看看std::list或者std::map它们都包含隐藏的类(或者是吗?)。关键是他们可能会也可能不会,但是因为实现是私有的和隐藏的,STL 的构建者能够更新代码而不影响你如何使用代码,或者在 STL 周围留下很多旧包袱,因为他们需要为了与一些决定要使用隐藏在list.

回答by Kos

Nested classes are just like regular classes, but:

嵌套类就像普通类,但是:

  • they have additional access restriction (as all definitions inside a class definition do),
  • they don't pollute the given namespace, e.g. global namespace. If you feel that class B is so deeply connected to class A, but the objects of A and B are not necessarily related, then you might want the class B to be only accessible via scoping the A class (it would be referred to as A::Class).
  • 他们有额外的访问限制(就像类定义中的所有定义一样),
  • 它们不会污染给定的命名空间,例如全局命名空间。如果您觉得类 B 与类 A 的联系如此紧密,但 A 和 B 的对象不一定相关,那么您可能希望类 B 只能通过对 A 类进行作用域来访问(它会被称为 A ::班级)。

Some examples:

一些例子:

Publicly nesting class to put it in a scope of relevant class

公开嵌套类以将其置于相关类的范围内



Assume you want to have a class SomeSpecificCollectionwhich would aggregate objects of class Element. You can then either:

假设您想要一个类SomeSpecificCollection来聚合 class 的对象Element。然后您可以:

  1. declare two classes: SomeSpecificCollectionand Element- bad, because the name "Element" is general enough in order to cause a possible name clash

  2. introduce a namespace someSpecificCollectionand declare classes someSpecificCollection::Collectionand someSpecificCollection::Element. No risk of name clash, but can it get any more verbose?

  3. declare two global classes SomeSpecificCollectionand SomeSpecificCollectionElement- which has minor drawbacks, but is probably OK.

  4. declare global class SomeSpecificCollectionand class Elementas its nested class. Then:

    • you don't risk any name clashes as Element is not in the global namespace,
    • in implementation of SomeSpecificCollectionyou refer to just Element, and everywhere else as SomeSpecificCollection::Element- which looks +- the same as 3., but more clear
    • it gets plain simple that it's "an element of a specific collection", not "a specific element of a collection"
    • it is visible that SomeSpecificCollectionis also a class.
  1. 声明两个类:SomeSpecificCollectionElement- 不好,因为名称“元素”足够通用以导致可能的名称冲突

  2. 引入命名空间someSpecificCollection并声明类someSpecificCollection::CollectionsomeSpecificCollection::Element。没有名称冲突的风险,但它可以变得更详细吗?

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

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

    • 您不会冒任何名称冲突的风险,因为 Element 不在全局命名空间中,
    • SomeSpecificCollection您的实现中,将 justElement和其他地方称为SomeSpecificCollection::Element- 看起来 +- 与 3. 相同,但更清楚
    • 很简单,它是“特定集合的元素”,而不是“集合的特定元素”
    • 可见SomeSpecificCollection也是一个类。

In my opinion, the last variant is definitely the most intuitive and hence best design.

在我看来,最后一个变体绝对是最直观的,因此也是最好的设计。

Let me stress - It's not a big difference from making two global classes with more verbose names. It just a tiny little detail, but imho it makes the code more clear.

让我强调一下 - 这与使用更冗长的名称制作两个全局类没有太大区别。这只是一个很小的细节,但恕我直言,它使代码更加清晰。

Introducing another scope inside a class scope

在类作用域内引入另一个作用域



This is especially useful for introducing typedefs or enums. I'll just post a code example here:

这对于引入 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
};

One then will call:

然后将调用:

Product p(Product::FANCY, Product::BOX);

But when looking at code completion proposals for Product::, one will often get all the possible enum values (BOX, FANCY, CRATE) listed and it's easy to make a mistake here (C++0x's strongly typed enums kind of solve that, but never mind).

但是在查看 的代码完成建议时Product::,通常会列出所有可能的枚举值(BOX、FANCY、CRATE),并且很容易在这里出错(C++0x 的强类型枚举可以解决这个问题,但没关系)。

But if you introduce additional scope for those enums using nested classes, things could look like:

但是,如果您使用嵌套类为这些枚举引入额外的作用域,事情可能如下所示:

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

Then the call looks like:

然后调用看起来像:

Product p(Product::ProductType::FANCY, Product::ProductBoxType::BOX);

Then by typing Product::ProductType::in an IDE, one will get only the enums from the desired scope suggested. This also reduces the risk of making a mistake.

然后通过Product::ProductType::在 IDE 中键入,您将仅从建议的所需范围中获得枚举。这也降低了犯错的风险。

Of course this may not be needed for small classes, but if one has a lot of enums, then it makes things easier for the client programmers.

当然,这对于小类来说可能不需要,但是如果有很多枚举,那么对于客户端程序员来说,这会让事情变得更容易。

In the same way, you could "organise" a big bunch of typedefs in a template, if you ever had the need to. It's a useful pattern sometimes.

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

The PIMPL idiom

PIMPL 习语



The PIMPL (short for Pointer to IMPLementation) is an idiom useful to remove the implementation details of a class from the header. This reduces the need of recompiling classes depending on the class' header whenever the "implementation" part of the header changes.

PIMPL(Pointer to IMPLementation 的缩写)是一种用于从头文件中删除类的实现细节的惯用语。这减少了在头文件的“实现”部分发生变化时根据类的头文件重新编译类的需要。

It's usually implemented using a nested class:

它通常使用嵌套类实现:

X.h:

Xh:

class X {
public:
    X();
    virtual ~X();
    void publicInterface();
    void publicInterface2();
private:
    struct Impl;
    std::unique_ptr<Impl> impl;
}

X.cpp:

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

This is particularly useful if the full class definition needs the definition of types from some external library which has a heavy or just ugly header file (take WinAPI). If you use PIMPL, then you can enclose any WinAPI-specific functionality only in .cppand never include it in .h.

如果完整的类定义需要来自某些外部库的类型定义,这些外部库具有沉重或丑陋的头文件(采用 WinAPI),则这特别有用。如果您使用 PIMPL,则您可以仅将任何 WinAPI 特定功能.cpp包含在.h.

回答by John Dibling

I don't use nested classes much, but I do use them now and then. Especially when I define some kind of data type, and I then want to define a STL functor designed for that data type.

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

For example, consider a generic Fieldclass that has an ID number, a type code and a field name. If I want to search a vectorof these Fields by either ID number or name, I might construct a functor to do so:

例如,考虑一个Field具有 ID 号、类型代码和字段名称的泛型类。如果我想通过 ID 号或名称搜索vector这些Fields 中的一个,我可能会构造一个函子来这样做:

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_;
  };
};

Then code that needs to search for these Fields can use the matchscoped within the Fieldclass itself:

然后需要搜索这些Fields 的代码可以使用类本身match内的作用域Field

vector<Field>::const_iterator it = find_if(fields.begin(), fields.end(), Field::match("FieldName"));

回答by Yeo

One can implement a Builder pattern with nested class. Especially in C++, personally I find it semantically cleaner. For example:

可以使用嵌套类实现 Builder 模式。特别是在 C++ 中,我个人觉得它在语义上更清晰。例如:

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

Rather than:

而不是:

class Product {}
class ProductBuilder {}