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

发布于 2024-10-10 05:13:53 字数 231 浏览 3 评论 0原文

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

但我仍然无法理解它们的用途。有人可以帮我吗?

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

暗藏城府 2024-10-17 05:13:53

嵌套类对于隐藏实现细节来说很酷。

列表:

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 的一部分,必须永远维护。通过将类设为私有,我不仅隐藏了实现,我还说这是我的,我可以随时更改它,这样您就无法使用它。

看看 std::liststd::map 它们都包含隐藏类(或者确实如此?)。关键是它们可能会也可能不会,但由于实现是私有的和隐藏的,STL 的构建者能够更新代码,而不会影响您使用代码的方式,或者在 STL 周围留下大量旧包袱,因为他们需要为了与一些决定使用隐藏在 list 中的 Node 类的傻瓜保持向后兼容性。

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.

Look at std::list or std::map they 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.

铁轨上的流浪者 2024-10-17 05:13:53

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

  • 它们具有额外的访问限制(就像类定义中的所有定义一样),
  • 它们不会污染给定的命名空间,例如全局命名空间。如果您觉得 B 类与 A 类联系如此之深,但 A 和 B 的对象不一定相关,那么您可能希望只能通过 A 类的作用域来访问 B 类(它将被称为 A ::班级)。

一些示例:

公开嵌套类以将其放入相关类的范围中


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

  1. 声明两个类:SomeSpecificCollectionElement - 不好,因为名称“Element”足够通用,可能会导致名称冲突< /p>

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

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

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

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

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

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

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


这对于引入 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(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 中。

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

Some examples:

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


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

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

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

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

  4. declare global class SomeSpecificCollection and class Element as its nested class. Then:

    • you don't risk any name clashes as Element is not in the global namespace,
    • in implementation of SomeSpecificCollection you 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 SomeSpecificCollection is also a class.

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:

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

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.

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.

The PIMPL idiom


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.

It's usually implemented using a nested class:

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

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 .cpp and never include it in .h.

晨曦÷微暖 2024-10-17 05:13:53

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

例如,考虑一个具有 ID 号、类型代码和字段名称的通用 Field 类。如果我想通过 ID 号或名称搜索这些 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 的代码code>Field 可以使用 Field 类本身范围内的 match

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

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.

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

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 match scoped within the Field class itself:

vector<Field>::const_iterator it = find_if(fields.begin(), fields.end(), Field::match("FieldName"));
孤独陪着我 2024-10-17 05:13:53

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

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

而不是:

class Product {}
class ProductBuilder {}

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

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

Rather than:

class Product {}
class ProductBuilder {}
聚集的泪 2024-10-17 05:13:53

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

I think the main purpose of making a class to be nested instead of being just a friend class is the ability to inherit nested class within derived one. Friendship is not inherited in C++.

内心旳酸楚 2024-10-17 05:13:53

您还可以考虑主要功能的一流类型,您可以在其中启动所有需要的类来一起工作。例如,像游戏类一样,启动所有其他类,如窗口、英雄、敌人、关卡等。这样你就可以从 main 函数中删除所有这些东西。您可以在其中创建游戏对象,并且可能执行一些与 Gemente 本身无关的额外外部调用。

You also can think about first class ass type of main function, where You initiate all needed classes to work togheter. Like for example class Game, initiate all other classes like windows, heroes, enemy's, levels and so on. This way You can get rid all that stuff from main function it self. Where You can create obiect of Game, and maybe do some extra external call not related to Gemente it self.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文