2013-07-26 119 views
1

的解析我试图解析与嵌套结构的二进制文件格式。在程序的伪代码,该过程将如下这样:嵌套结构和对象模型

// A structure contains: 
// tag | oneof(a, b, c) | oneof(oneof(aa, ab, ac), oneof(ba, bb, bc), oneof(ca, cb, cc)) 
PROCEDURE parse() { 
    RECORD read_type; 

    read_tag(read_type); 

    if (read_type == TYPE_A) { 
     read_a(read_type); 
     if (read_type == TYPE_AA) { 
      read_aa(); 
     } else if (read_type == TYPE_AB) { 
      read_ab(); 
     } else if (read_type == TYPE_AC) { 
      read_ac(); 
     } 
    } else if (read_type == TYPE_B) { 
     // see above 
    } else if (read_type == TYPE_C) { 
     // see above 
    } 
} 

的外部结构例如AA不能在不脱离它的父对象A,而这又需要它的标签/标头解释上下文来解释。使用这些结构时,处理包含A的结构是有意义的,这些结构包含AA等,但不能是结构的A或AA部分。

我的问题是那么如何创建此过程的类模型。如果该结构是:

class Base; 
class A: Base; 
class B: Base; 
class C: Base; 
class AA: A; 
class AB: A; 
class AC: A; 
// ... 

在这种情况下,AA可能被理解为这样的:

AA::AA(): A() { 
    read_aa(); 
} 

A::A(): Base() { 
    read_a(); 
} 

Base::Base() { 
    read_tag(); 
} 

然而,问题是,这是不可能知道的派生类对象,而不构建首先构造基础对象。这可以通过使用构造函数AA :: AA(A *)来复制构造其父项,但这看起来像是一种不必要的低效率。此外,这将需要一个外部工厂功能,诸如:

Base *read_object() { 
    Base *base = new Base(); 
    if (b->tag_type == TYPE_A) { 
     A *a = new A(base); 
     if (a->tag_type == TYPE_AA) { 
      return new AA(a); 
     } else if (a->tag_type == TYPE_AB) { 
      // ... 
     } else if (a->tag_type == TYPE_AC) { 
      // ... 
     } 
    } else if (b->tag_type == TYPE_B) { 
     // ... 
    } else if (b->tag_type == TYPE_C) { 
     // ... 
    } 
} 

另一种选择是为具有指结构的子区域,如类:

class CompleteStructure; 
class StructureA; 
class StructureB; 
class StructureC; 
class StructureAA; 
class StructureAB; 
class StructureAC; 
// ... 

class CompleteStructure { 
    union {StructureA a, StructureB b, StructureC c} sub; 
} 

class StructureA { 
    CompleteStructure *parent; 
    union {StructureAA aa, StructureAB ab, StructureAC ac} sub; 
} 

class StructureAA { 
    StructureA *parent; 
} 

在这种情况下,该构造函数CompleteStructure :: CompleteStructure()会读取标记,然后构造StructureA,StructureB或StructureC中的一个,然后构造它自己的子结构。与此相关的问题是,每个子结构都需要明确引用其父项,以便“投射”层次并实现其方法/功能。

是这些方法比其他的更好的空间/时间效率和“清洁”的名词之一?有没有优越的第三种方法?

编辑: 要到下面的两个答案回应,问题是双方关于分析和对象行为。我最初的目标仅仅是从文件中读取结构,打印出它们的字段,然后按照相同的顺序将它们写回到磁盘。稍后,还会有额外的目标,例如查找A派生结构的所有实例并按特定字段对其进行排序或检查非法结构组合(例如,同时具有BA和BB)。

EDIT2: 这是我参考的一个结构(带有通用字段名称)的示例模式。 u8/16/32是指整数类型,sz是一个C字符串,大写名称是需要读取的字段,常量前缀为下划线。

DEF AA { 
    // Identifies and deliminates complete records. 
    TAG { 
     u32 SYNC_CODE = 0xFFFFFFFF; 
    } 

    // Metadata for high level identification of data. 
    A { 
     u32 TYPE = __TYPE_A; 
     u16 CATEGORY = __CATEGORY_1; // A defines the "category" of the following file data 
     u32 NUM_OF_KV_PAIRS; 
     for (int i = 0; i < NUM_OF_KV_PAIRS; ++i) { // unspecified metadata 
      sz KEY; 
      sz VALUE; 
     } 
     u8 HAS_EXTENSION_FLAG = true; // indicates presence of next record 
     if (!HAS_EXTENSION_FLAG) { 
      DEFAULT_PARAMS; // legacy 
     } 
    } 

    // Indicates a specific data layout and version. 
    AA { 
     u32 TYPE = __TYPE_AA; 
     u8[16] ACCESS_KEY; 
     u32 NUM_OFFSETS; 
     for (int i = 0; i < NUM_OFFSETS; ++i) { 
      // stuff 
     } 
    } 
} 
+0

我没有得到_“与此相关的问题是,每个子结构都需要明确引用其父级以便”投射“层次结构”_您能详细说明一点吗?为什么一个子结构让我们说AA需要知道任何关于它的超级结构A的例子? –

+0

AA中的字段指的是先前结构中的初始化结构来定义它们的语义。此外,为了类型安全,人们可能希望通过其特定类型来引用完整的结构,例如函数'process(AA a){do_something(a.complete_structure()); }'。 – user2363399

+0

_HAS_EXTENSION_FLAG = true; //表示下一个记录的存在_这是关于AA结构的存在还是其他意义? –

回答

0

这是很难回答的,如果一些做法是在效益分析方面没有更好的更具体的问题说明。在下面你可以找到一些思考的食物。

要点1:在考虑班级设计时,还需要考察所需的行为而不仅仅是数据。用于存储的二进制格式不一定意味着层次结构,当然应该考虑到这一点,但它不应该成为主要关注点。

举个例子,假设我们有一个Person类具有height场和Rectangle类,也有height场。他们都分享一些数据,但只有这些信息使他们彼此无关。如果我们定义了上下文,并且我们说我们想要在屏幕上绘制它们,那么突然height字段具有更具体的含义。现在继承Drawable也许更有意义。

您的情况中的问题是我们将如何使用它们?如果我们有{A, B}{AA, BB}甚至{A, BB}的列表,我们可以执行哪些常见操作?我们能以某种方式一起管理它们吗?这是你应该考虑的重要一点。

点2:你说“是有意义的操作包含一个包含AA等结构,但绝不只有A或结构的AA部分”。所以我明白AAis-aA,但反过来也是如此。如果是这样的话,那么它很有意义的Base, A, B, C抽象类,只能够直接instatiate最后一级AA, BB

3点:在另一方面,它可能会更好超过可用的组合物如果不同的结构只是定义了一些数据而不是一些行为,就是继承。例如。我们会调用一个像process()这样的方法来处理数据吗?或者我们是否想将这些结构本身用作数据?

class X { 
    Base base; 
    A a; 
    AA aa; 
    process() { 
     // this is different than calling base.process() + a.process() + aa.process() 
     // do we need one over the other? both? 
     process(base) + process(a) + process(aa); 
    } 
} 

点4:关于初始化顺序一边读书,这不应该是一个问题。也许你可以在临时存储它时读取这些信息,并且只有在知道它的完整类型(即达到最后一级)后才能实例化一个类。

我希望帮助

+0

我已经更新了问题以澄清第1点。关于第4点,这不需要额外复制/读取数据吗? – user2363399

+0

@ user2363399它需要一个额外的副本是(所有实例不是每个实例)。另一种阅读我不这么认为。我正在考虑一个例子:'A value1 = readValue(); value2 = readValue(); ...最后'新的AA(value1,value2,...);'对于下一个实例值可以重用。我不知道你的结构有多大,如果这是可能的。我只是提供想法 –

+0

这些将是可以从几个字节到几个kB/MB(或者甚至可能是GB,但是由于它们包含原始阵列而将我实现为句柄的范围)的批量结构。一般来说,我想保持数据连续以避免间接指向,但也许这是错误的? – user2363399

0

的问题没有明确说明你认为你正在做的,或者实际的问题是什么(即你应该做的事情)。

你需要非常清楚地界定其A,AA型,AB型是实体用自己独特的生存 - 而哪里有子关系这你理应解析。你说嵌套结构,但没有详细说明。

作为另一个提到的答案 - OO约为行为,而不是约数据建模

严重依赖继承,特别是因为你不知道你在构建什么,听起来像是一个完全的错误。当你需要行为时(通过计算或做事的方法),一般的继承问题只会有用,它可以有效地将行为空间划分为一些类层次,并从中受益。

上面提到的问题只是一个解析问题。你可以使用堆栈和一些内部状态(比如说一个StringBuilder,最简单的方法)来读取&建立解析状态,同时使用堆栈来推动弹出嵌套层次的&。

实际上,以上是实现大多数解析器的好方法。

一个更复杂的选择(在解析器中也很常见)是构建一个AST。这些都是非常高效的&轻量级构建&遍历。

class AstNode { 
    protected AstNode down;  // first child. 
    protected AstNode across; // next sibling. 

    public void addChild (AstNode child) { 
     if (getDown() == null) { 
      // First Child; 
      this.down = child; 
      return; 
     } 
     // Sibling to existing Children. 
     AstNode last = down; 
     while (last.getAcross() != null) 
      last = last.getAcross(); 
     last.across = child; 
     // done. 
    } 
} 

随着AST,你也可以把属性/成员的节点类型,数据类型(词汇)等,并有效地建立在自己的权利强大的数据结构。

希望这会有所帮助。

+0

是的,你说得对,解析是这个的重要组成部分。我认为AST会过度杀伤,因为这是一种二进制格式,没有复杂的优先规则,或者在例如编程语言。我认为某种树可能是合适的。结构的每个部分都可以是树中的一个节点,尽管这并不像我所希望的那样真正利用语言类型系统。 – user2363399

+0

AST没有体现优先规则或者支持它们的任何开销,它只是存储输出的轻量和可靠的方式(有或没有优先级)。基于栈的解析和解析器规则是实际应用语法规则和优先级的。 –

+0

利用类型系统?如果您将数据成员放入您的AST或(有时)数据和类型成员中,则可以在解析进一步发展后附加结构化对象,并在这些对象上使用语言的类型系统。效果很好。 –