我正在尝试使用嵌套结构解析二进制文件格式。在过程伪代码中,过程如下:
// 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
}
}
如果没有来自其父对象 A 的上下文,则无法解释诸如 AA 之类的外部结构,这反过来又需要其标签/标题来解释。在使用这些结构时,操作包含 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
}
}
}