使用嵌套 C++ 类和枚举的优缺点?

Pros and cons of using nested C++ classes and enumerations?

提问人:Rob 提问时间:10/20/2008 最后编辑:JasonMArcherRob 更新时间:6/7/2015 访问量:24925

问:

使用嵌套的公共 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++ 枚举

评论


答:

7赞 Adam Rosenfield 10/20/2008 #1

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

评论

0赞 paercebal 10/20/2008
请注意,使用命名空间嵌套类使用户能够转发声明此类:即: <code>namespace Foo { class Bar ; }</代码>。无论如何,+1 表示评论。
48赞 paercebal 10/20/2008 #2

嵌套类

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

让我们想象一下下面的代码:

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

   // etc.
} ;

甚至:

class A
{
   public :
      class B ;

   // etc.
} ;

class A::B
{
   public :

   // etc.
} ;

所以:

  • 特权访问:A::B 具有对 A 的所有成员(方法、变量、符号等)的特权访问权,这削弱了封装
  • A 的作用域是符号查找的候选对象:B 内部的代码会将 A 中的所有符号视为符号查找的可能候选对象,这可能会混淆代码
  • 前瞻声明:如果不给出 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?

Ony 通过将每个枚举放在不同的命名空间/类中,可以避免这种冲突:

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++0 定义了类枚举:

enum class Value { empty, undefined, defined } ;
enum class Glass { empty, half, full } ;

// Value e = Value::empty ;
// Glass f = Glass::empty ;

正是针对这种问题。

评论

2赞 Martin York 10/20/2008
嵌套私有类的一个很好的例子是 std::list 内列表中的链接。任何人都不需要知道它们,使用它们或与它们互动(或者它们真的存在;-)
0赞 xtofl 10/20/2008
我确实时不时地为 pImpl-pattern 使用嵌套的私有类。
1赞 paercebal 1/1/2009
PImpl 模式中的嵌套私有类意味着用户将看到实现类,即使它不可访问。有一个简单的转发声明只会声明名称,而没有其他任何内容。您将能够在其他地方完全定义实现(对用户隐藏)...... ^_^
1赞 Catskul 10/2/2009
转发申报问题不仅仅是一种不便。您可能会陷入需要取消嵌套问题区域的依赖循环中。取消嵌套可能会造成设计不一致,或者您将不得不将整个项目更改为取消嵌套:/stackoverflow.com/questions/951234/...
0赞 paercebal 2/7/2010
经过进一步的调查,我完成了“嵌套类”部分(由与朋友的技术讨论提示)。结果是相同的(除非异常,嵌套类是不行的),但原因更详细。
2赞 Paul Nathan 10/20/2008 #3

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

当你想将“内部”类作为一个对象本身使用时,事情就会开始变得有点混乱,你必须开始编写提取器/插入器例程。这不是一个漂亮的情况。

2赞 carson 10/20/2008 #4

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

0赞 DavidG 10/20/2008 #5

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

1赞 Nick 10/20/2008 #6

paercebal 说了我想说的关于嵌套枚举的一切。

WRT嵌套类,我常见且几乎唯一的用例是,当我有一个操作特定类型资源的类时,我需要一个数据类来表示特定于该资源的东西。就您而言,output_tray可能是一个很好的示例,但是如果该类将具有任何将从包含类外部调用的方法,或者主要不仅仅是一个数据类,我通常不会使用嵌套类。我通常也不会嵌套数据类,除非包含的类从未在包含类之外直接引用。

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

希望这会有所帮助。:)

0赞 Pat Notz 10/20/2008 #7

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

因此,是的,如果其他代码仅使用该枚举直接与该具体类交互,则将枚举嵌入到类中。否则,请找到更好的位置来保存枚举,例如命名空间。

0赞 Mark Ransom 10/20/2008 #8

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

0赞 Brian Stewart 10/21/2008 #9

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

-1赞 Marius Amado-Alves 5/31/2013 #10

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

(但也许有办法?

评论

0赞 Oswald 9/1/2013
这里有一个混乱。嵌套类并不意味着嵌套对象。对于类型为 A::B 的对象 b,可能不存在类型为 A 的对象,b 是该对象的成员。如果你想要这样的关系,你必须自己建立一个。
0赞 Marius Amado-Alves 9/11/2013
你是对的,可能没有。但很多时候都有。这个成语很常见
0赞 Marius Amado-Alves 9/11/2013
你是对的,可能没有。但很多时候都有。这个成语很常见: class A { int m; class B { int getM(); }乙乙;} 其中 B 对象始终是真实 A 对象的组件。如果C++添加一个“parent”,就像她有“this”一样,你可以这样实现getM: int A::B::getM() { return parent->m;(这只是学术上的。
2赞 Oswald 9/1/2013 #11

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

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

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

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

关于嵌套类的另一个事实是,除非您是 A 的所有者,否则不可能将另一个类 A::C 添加为 A 的嵌套类。但是,这是完全合理的,因为嵌套类具有特权访问。如果可以将 A::C 添加为 A 的嵌套类,那么 A::C 可以诱骗 A 授予对特权信息的访问权限;并且您违反了封装。这与声明基本相同:声明不授予您任何特权,您的朋友正在向他人隐瞒;它允许您的朋友访问您向非朋友隐藏的信息。在 C++ 中,称某人为朋友是一种利他行为,而不是利己主义行为。这同样适用于允许类是嵌套类。friendfriend

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

  • A 的作用域是 B 的符号查找的候选对象:如果您不希望这样做,请让 B 成为 A 的朋友,而不是嵌套类。但是,在某些情况下,您完全需要这种符号查找。
  • A::B 不能前向声明:A 和 A:B 紧密耦合。能够在不知道 A 的情况下使用 A::B 只会掩盖这一事实。

总结一下:如果工具不符合你的需求,不要责怪工具;责怪自己使用该工具;其他人可能有不同的问题,该工具非常适合这些问题。

0赞 Yaroslav Nikitenko 9/11/2013 #12

我可以看到嵌套类的一个缺点,即可以更好地使用泛型编程。

如果小类是在大类之外定义的,则可以将大类设置为类模板,并将来可能需要的任何“小”类用于大类。

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

1赞 Eric G. 3/29/2014 #13

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