简体   繁体   English

C ++类排序

[英]C++ class ordering

I'm starting to play around with C++, coming from C and Objective C (and a bit of Java). 我开始使用C ++,来自C和Objective C(以及一些Java)。 I thought a good place to start building my skills is by writing a simple hash table from scratch, using linked lists for collisions. 我认为开始构建我的技能的好地方是从头开始编写一个简单的哈希表,使用链接列表进行冲突。 So I started out by writing the skeletons for each class. 所以我开始为每个班级编写骨架。

class HashTable
{
   public:
     ...
   private:
     ...
};

class LinkedList
{
   public:
     ...
   private:
     Node *root;
};

class Node
{
  public:
    Node *next;
    string key;
    int value;
    Node()
    {
      ...
    }
};

The weird thing about this is, and this may not come as any surprise to c++ users, that this code wouldn't work. 关于这一点的奇怪之处在于,对于c ++用户来说,这可能不会让这个代码无效。 I would get an error like: 我会得到一个错误:

error: expected type-specifier before ‘Node’

with respect to the root node in LinkedList class. 关于LinkedList类中的根节点。

When I simply reordered the classes so that it was Node{...}; LinkedList{...}; HashTable{...}; 当我简单地重新排序类以便它是Node{...}; LinkedList{...}; HashTable{...}; Node{...}; LinkedList{...}; HashTable{...}; everything worked like a well oiled ice cream truck. 一切都像一个油润的冰淇淋卡车。

Now, I'm not one to question the design of C++, but is there any reason for this limitation? 现在,我不是一个质疑C ++设计的人,但这个限制有什么理由吗? If I remember correctly, Obj. 如果我没记错的话,Obj。 C's class's are essentially turned into tables and looked up on the fly. C的课程基本上变成了桌子,并在飞行中查找。 So what's reason for this behavior? 那么这种行为的原因是什么?

The compiler throws the following error 编译器抛出以下错误

error: expected type-specifier before ‘Node’

because it (the compiler) does not (yet) know 因为它(编译器)还没有(还)知道

Node *root;

what Node is. Node是什么。 (Since the Node is defined later.) (因为Node稍后定义。)

Two possible solutions: 两种可能的解决方

  • Put the definition of Node class before LinkedList class (you already know this) LinkedList类之前放置Node类的定义(你已经知道了)

  • Forward declare the class Node before class LinkedList by putting this line 通过放置此行,Forward在类LinkedList之前声明类Node

    class Node; class Node;

    This tells compiler that there exists a class Node . 这告诉编译器存在一个类Node

After reading PigBen 's comment, it seems you are questioning the rationale for this behavior. 在阅读了PigBen的评论之后,您似乎在质疑这种行为的基本原理。 I am not a compiler person, but I think that this behavior makes it easy for parsing. 我不是编译人员,但我认为这种行为使解析变得容易。 To me, it is similar to having a function declaration available before its use. 对我来说,它类似于在使用之前提供函数声明。

PS: Nitpick, for LinkedList , a variable name head may be more suitable than root . PS:Nitpick,对于LinkedList ,变量名head可能比root更合适。

The requirement for declarations of this sort comes from two forces. 这种声明的要求来自两种力量。 The first is that it simplifies compiler design. 首先,它简化了编译器设计。 Since types and variables have the same identifier structure, the compiler must know which it is encountering whenever it does parse an identifier. 由于类型和变量具有相同的标识符结构,因此只要编译器解析标识符,编译器就必须知道它遇到了什么。 There are two ways to do this. 有两种方法可以做到这一点。 One way would be to require that every identifier be declared before it may be used in other definitions. 一种方法是要求在将其用于其他定义之前声明每个标识符。 This means that the code must forward declare any name it intends to use before giving its definition. 这意味着代码必须在给出其定义之前转发声明它打算使用的任何名称。 This is a very easy way to write a compiler with an otherwise ambiguous grammar. 这是编写具有其他模糊语法的编译器的一种非常简单的方法。

The other way to do this is to handle it in multiple passes. 另一种方法是在多次传递中处理它。 Any time an undeclared identifier is encountered, it is skipped, and the compiler tries to resolve it once it's parsed the whole file. 每次遇到未声明的标识符时,都会跳过它,编译器会在解析整个文件后尝试解析它。 It turns out that the grammar of C++ makes this very difficult to do correctly. 事实证明,C ++的语法使得这很难正确完成。 Compiler writers didn't want to have to go to this trouble, and so we have forward declarations. 编译器编写者不想不得不去解决这个问题,所以我们有前向声明。

The other reason is that you may actually want to have forward declarations so that recursive structures are determinite as an intrinsic property of the language. 另一个原因是您实际上可能希望具有前向声明,以便递归结构是确定性的,作为语言的内在属性。 This is a bit more subtle. 这有点微妙。 Suppose you had written a mutually recursive class network: 假设您编写了一个相互递归的类网络:

class Bar; // forward declaration
class Foo {
    Bar myBar;
};

class Bar {
    int occupySpace;
    Foo myFoo;
};

This is obviously impossible, because the occupySpace member would appear in an infinitely nested recursion. 这显然是不可能的,因为occupySpace成员将出现在无限嵌套的递归中。 requiring that a forward declaration of all members in a definition provides a specific amount of information for this. 要求定义中所有成员的前向声明为此提供特定数量的信息。 In particular, it allows the compiler enough information to form a reference to a class, but not to instantiate the class (because it's size is not known). 特别是,它允许编译器提供足够的信息来形成对类的引用,但不允许实例化类(因为它的大小未知)。 The forward declarations make this a feature of the syntax of the language, much like how lvalues are assignable as a feature of the language syntax rather than a more subtle semantic or run-time requirement. 前向声明使这成为语言语法的一个特征,就像左值如何作为语言语法的特征而不是更微妙的语义或运行时要求一样可分配。

The reason for this behavior is historical. 这种行为的原因是历史性的。 The file is processed sequentially. 文件按顺序处理。 At the time it comes across the first reference to an identifier, that identifier needs to have already been declared. 在遇到标识符的第一个引用时,该标识符需要已经声明。

The compiler does not process the whole file first. 编译器不会首先处理整个文件。

Instead of re-ordering the class definitions, you can often get away with a forward declaration 您可以经常使用前向声明,而不是重新排序类定义

class Node;

class List
{
    public:
    //...
    private:
    Node *root;
    //...
};

//...

There is no technical reason for this limitation - that is proven by the fact that compiler do what you evidently expect within the context of a single class. 这种限制没有技术上的原因 - 这可以通过编译器在单个类的上下文中执行您明显期望的事实来证明。 Still, removing the "limitation" does complicate compilers further, slow them down, increase their memory usage, and crucially - would not be backwards compatible (as matches in a more localised scope (namespace) would presumably be selected over other symbols seen earlier). 尽管如此,删除“限制”确实会进一步使编译器复杂化,减慢它们的速度,增加它们的内存使用量,而且至关重要的是 - 不会向后兼容(因为在更局部化的范围(命名空间)中的匹配可能会被选中而不是之前看到的其他符号) 。

IMHO, it also makes code harder to read and understand. 恕我直言,它也使代码更难阅读和理解。 Being able to read from top to bottom and comprehend the code as you go is very useful, and encourages more thoughtful and structured expression of your problem solution. 能够从上到下阅读并随时理解代码非常有用,并鼓励您更有思想和结构化地表达您的问题解决方案。

Think about it the other way around; 反过来考虑一下; if it's reasonable to accept a class declared anywhere in the file as OK, why not a class declared in another file that has yet to be encountered? 如果接受在文件中任何地方声明的类是正确的,为什么不在另一个尚未遇到的文件中声明的类呢?

If you go that far, then you end up not being able to give an error until you try to link the program, which may be far away from where the problem actually occurs. 如果你走得那么远,那么在你尝试链接程序之前你最终无法给出错误,这可能远离问题实际发生的地方。

You've declared that your LinkedList class has a type of Node but the compiler doesn't know what a Node is because its yet to be declared. 您已声明您的LinkedList类具有Node类型,但编译器不知道Node是什么,因为它尚未声明。

Just declare Node before LinkedList 只需在LinkedList之前声明Node

This style means that the parser can run through the code fewer times. 这种风格意味着解析器可以更少次地运行代码。 If you must identify every declared type, then run through the code again, you spend extra time parsing with the second run through the file. 如果必须识别每个声明的类型,然后再次运行代码,则需要花费额外的时间来解析第二次运行文件。

Of course, as so many have pointed out, you can use a forward declaration. 当然,正如许多人所指出的那样,你可以使用前瞻性声明。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM