【问题标题】:Pros and cons of using nested C++ classes and enumerations?使用嵌套 C++ 类和枚举的优缺点?
【发布时间】:2010-09-18 00:30:23
【问题描述】:

使用嵌套的公共 C++ 类和枚举有哪些优缺点?例如,假设您有一个名为printer 的类,并且该类还存储了输出托盘上的信息,您可以:

class printer
{
public:
    std::string name_;

    enum TYPE
    {
        TYPE_LOCAL,
        TYPE_NETWORK,
    };

    class output_tray
    {
        ...
    };
    ...
};

printer prn;
printer::TYPE type;
printer::output_tray tray;

或者:

class printer
{
public:
    std::string name_;
    ...
};

enum PRINTER_TYPE
{
    PRINTER_TYPE_LOCAL,
    PRINTER_TYPE_NETWORK,
};

class output_tray
{
    ...
};

printer prn;
PRINTER_TYPE type;
output_tray tray;

我可以看到嵌套私有枚举/类的好处,但是当涉及到公共枚举/类时,办公室是分开的——这似乎更像是一种风格选择。

那么,你更喜欢哪一个,为什么?

【问题讨论】:

    标签: c++ class enums nested


    【解决方案1】:

    嵌套类

    嵌套在类中的类有几个副作用,我通常认为是缺陷(如果不是纯粹的反模式)。

    让我们想象下面的代码:

    class A
    {
       public :
          class B { /* etc. */ } ;
    
       // etc.
    } ;
    

    甚至:

    class A
    {
       public :
          class B ;
    
       // etc.
    } ;
    
    class A::B
    {
       public :
    
       // etc.
    } ;
    

    所以:

    • 特权访问: A::B 对 A 的所有成员(方法、变量、符号等)具有特权访问权限,这削弱了封装性
    • A 的作用域是符号查找的候选对象: B 内部的代码会将 A 中的 所有 个符号视为符号查找的可能候选对象,这会混淆代码
    • forward-declaration: 如果不给出 A 的完整声明,就无法前向声明 A::B
    • 可扩展性:除非您是 A 的所有者,否则无法添加另一个类 A::C
    • 代码冗长:将类放入类只会使标题变大。您仍然可以将其分成多个声明,但无法使用类似命名空间的别名、导入或使用。

    作为一个结论,除非有例外(例如嵌套类是嵌套类的亲密部分......即使那样......),我认为在普通代码中嵌套类没有任何意义,因为缺陷超过了数量级感知到的优势。

    此外,在不使用 C++ 命名空间的情况下模拟命名空间是一种笨拙的尝试。

    在专业方面,您隔离此代码,如果是私有的,则使其无法使用,但来自“外部”类...

    嵌套枚举

    优点:一切。

    缺点:什么都没有。

    事实上枚举项会污染全局范围:

    // collision
    enum Value { empty = 7, undefined, defined } ;
    enum Glass { empty = 42, half, full } ;
    
    // empty is from Value or Glass?
    

    只有将每个枚举放在不同的命名空间/类中才能避免这种冲突:

    namespace Value { enum type { empty = 7, undefined, defined } ; }
    namespace Glass { enum type { empty = 42, half, full } ; }
    
    // Value::type e = Value::empty ;
    // Glass::type f = Glass::empty ;
    

    注意C++0x定义了类枚举:

    enum class Value { empty, undefined, defined } ;
    enum class Glass { empty, half, full } ;
    
    // Value e = Value::empty ;
    // Glass f = Glass::empty ;
    

    正是针对这类问题。

    【讨论】:

    • 嵌套私有类的好例子是 std::list 内的列表中的链接。没有必要让任何人知道他们使用它们或与它们互动(或者它们是否真的存在;-)
    • 我确实时不时地为 pImpl 模式使用嵌套的私有类。
    • PImpl 模式中的嵌套私有类意味着用户将看到实现类,即使它不可访问。有一个简单的前向声明只会声明名称,没有别的。您将能够在其他地方完全定义实现(对用户隐藏)... ^_^ ..
    • 前向声明问题不仅仅是一个不便。您可能会陷入需要取消嵌套问题区域的依赖循环。取消嵌套可能会导致设计不一致,或者您必须将整个项目更改为取消嵌套:/stackoverflow.com/questions/951234/…
    • 我在进一步调查后完成了“嵌套类”部分(与朋友进行技术讨论)。结果是一样的(除非例外,嵌套类不好),但原因更详细。
    【解决方案2】:

    对于大型项目来说,一个可能成为大问题的缺点是不可能为嵌套类或枚举进行前向声明。

    【讨论】:

    • 请注意,使用命名空间嵌套类使用户能够前向声明该类:即:namespace Foo { class Bar ; }代码>。无论如何,为评论+1。
    【解决方案3】:

    在我看来,如果除了使用独立类的实现之外,您永远不会将依赖类用于任何事情,那么嵌套类就可以了。

    当你想将“内部”类作为一个对象使用时,事情可能会开始变得有点笨拙,你必须开始编写提取器/插入器例程。情况不妙。

    【讨论】:

      【解决方案4】:

      您似乎应该使用命名空间而不是类来以这种方式对彼此相关的事物进行分组。我在嵌套类中看到的一个缺点是,你最终会得到一个非常大的源文件,当你搜索一个部分时可能很难理解。

      【讨论】:

        【解决方案5】:

        使用嵌套的公共 C++ 类本身没有优缺点。只有事实。这些事实是 C++ 标准规定的。关于嵌套公共 C++ 类的事实是优点还是缺点取决于您要解决的特定问题。您给出的示例无法判断嵌套类是否合适。

        关于嵌套类的一个事实是,它们有权访问它们所属的类的所有成员。如果嵌套类不需要这种访问,这是一个缺点。但是如果嵌套类不需要这样的访问,那么它就不应该被声明为嵌套类。在某些情况下,当一个类 A 想要授予对某些其他类 B 的特权访问。这个问题有三种解决方案

        1. B成为A的朋友
        2. 使 B 成为 A 的嵌套类
        3. B 需要的方法和属性设为A 的公共成员。

        在这种情况下,违反封装的是 #3,因为 A 可以控制他的朋友和他的嵌套类,但不能控制调用他的公共方法或访问他的公共属性的类。

        关于嵌套类的另一个事实是,除非您是 的所有者,否则不可能将另一个类 A::C 添加为 A 的嵌套类一个。然而,这是完全合理的,因为嵌套类具有特权访问。如果可以将 A::C 添加为 A 的嵌套类,那么 A::C 可以欺骗 A 授予对特权信息的访问权限;并且你会违反封装。与friend 声明基本相同:friend 声明不授予您任何特权,您的朋友对其他人隐藏;它允许您的朋友访问您对非朋友隐藏的信息。在 C++ 中,称某人为朋友是一种利他行为,而不是自私行为。允许类成为嵌套类也是如此。

        关于嵌套公共类的其他一些事实:

        • A 的作用域是 B 的符号查找的候选对象:如果你不想要这个,让 B 成为 A 的朋友而不是嵌套类.但是,在某些情况下,您确实需要这种符号查找。
        • A::B 不能前向声明:AA::B 紧密耦合。能够在不知道 A 的情况下使用 A::B 只会隐藏这个事实。

        总结一下:如果工具不能满足你的需求,不要责怪工具;责备自己使用该工具;其他人可能有不同的问题,该工具是完美的。

        【讨论】:

          【解决方案6】:

          paercebal 说了所有关于嵌套枚举的内容。

          WRT 嵌套类,我的常见且几乎唯一的用例是当我有一个正在操作特定类型资源的类时,我需要一个代表该资源特定内容的数据类。在您的情况下, output_tray 可能是一个很好的例子,但如果该类将具有将从包含类外部调用的任何方法,或者不仅仅是一个数据类,我通常不使用嵌套类。我通常也不嵌套数据类,除非包含的类从未在包含的类之外直接引用。

          因此,例如,如果我有一个 printer_manipulator 类,它可能有一个包含打印机操作错误的类,但打印机本身将是一个非包含类。

          希望这会有所帮助。 :)

          【讨论】:

            【解决方案7】:

            请记住,您以后总是可以将嵌套类提升为顶级类,但如果不破坏现有代码,您可能无法执行相反的操作。因此,我的建议是先把它做成一个嵌套类,如果它开始出现问题,在下一个版本中让它成为一个顶级类。

            【讨论】:

              【解决方案8】:

              对我来说,把它放在外面的一个很大的缺点是它成为了全局命名空间的一部分。如果枚举或相关类只真正适用于它所在的类,那么它是有道理的。因此,在打印机案例中,包括打印机在内的所有内容都将知道对枚举 PRINTER_TYPE 具有完全访问权限,而实际上并不需要知道它。我不能说我曾经使用过内部类,但是对于枚举,将其保留在内部似乎更合乎逻辑。正如另一位发帖人所指出的,使用命名空间对相似的项目进行分组也是一个好主意,因为阻塞全局命名空间确实是一件坏事。我之前从事过大型项目,仅在全局命名空间上显示一个自动完成列表就需要 20 分钟。在我看来,嵌套枚举和命名空间类/结构可能是最干净的方法。

              【讨论】:

                【解决方案9】:

                我同意那些主张将枚举嵌入到类中的帖子,但在某些情况下,不这样做更有意义(但请至少将它放在命名空间中)。如果多个类使用在不同类中定义的枚举,则这些类直接依赖于其他具体类(拥有该枚举)。这肯定代表了一个设计缺陷,因为该类将负责该枚举以及其他职责。

                所以,是的,如果其他代码仅使用该枚举直接与该具体类交互,则将枚举嵌入到一个类中。否则,找一个更好的地方来保存枚举,比如命名空间。

                【讨论】:

                  【解决方案10】:

                  如果您将枚举放入类或命名空间中,智能感知将能够在您尝试记住枚举名称时为您提供指导。肯定是小事,但有时小事很重要。

                  【讨论】:

                    【解决方案11】:

                    Visual Studio 2008 似乎无法为嵌套类提供智能感知,因此在我曾经拥有嵌套类的大多数情况下,我已切换到 PIMPL 习惯用法。如果枚举仅由该类使用,我总是将枚举放在类中,或者当多个类使用枚举时,将枚举放在与该类相同的命名空间中。

                    【讨论】:

                      【解决方案12】:

                      我可以看到嵌套类的一个缺点,那就是最好使用泛型编程。

                      如果小类是在大类之外定义的,您可以将大类作为类模板,并将您将来可能需要的任何“小”类与大类一起使用。

                      通用编程是一个强大的工具,恕我直言,我们在开发可扩展程序时应该牢记这一点。奇怪,没有人提到这一点。

                      【讨论】:

                        【解决方案13】:

                        我遇到的嵌套类的唯一问题是 C++ 不允许我们在嵌套类函数中引用封闭类的对象。我们不能说“Enclosure::this”

                        (但也许有办法?)

                        【讨论】:

                        • 这里有点混乱。 嵌套类并不意味着嵌套对象。对于 A::B 类型的对象 b,可能没有 A 类型的对象,即 b 是其中的一员。如果你想要这样的关系,你必须自己建立一个。
                        • 你是对的,可能没有。但很多时候是有的。这个成语很常见
                        • 你是对的,可能没有。但很多时候是有的。这个成语很常见:class A { int m; B类 { int getM(); } 乙乙;其中 B 对象始终是真实 A 对象的组件。如果 C++ 添加一个“父”,就像她有“这个”,你可以这样实现 getM: int A::B::getM() { return parent->m; }(这只是学术性的。)
                        猜你喜欢
                        • 2011-12-14
                        • 1970-01-01
                        • 2012-10-26
                        • 2019-09-12
                        • 1970-01-01
                        • 2011-01-20
                        • 1970-01-01
                        • 2021-01-23
                        • 1970-01-01
                        相关资源
                        最近更新 更多