用 C 编写一个安全的标记联合

ces*_*sss 5 c unions

假设您正在编写一个C struct,它代表一顿饭中的一道菜。课程中的字段之一struct是以下类型:

enum TP_course {STARTER, MAINCOURSE, DESSERT};
Run Code Online (Sandbox Code Playgroud)

然后,根据课程类型,您有一个子类型:

enum TP_starter {SALAD, GRILLEDVEGETABLES, PASTA};
enum TP_maincourse {BEEF, LAMB, FISH};
enum TP_dessert {APPLEPIE, ICECREAM, MOUSSE};
Run Code Online (Sandbox Code Playgroud)

鉴于一次只使用一个这样的枚举(取决于课程的类型),将它们聚合在一个 中是有意义的union

union U_subtype {
   enum TP_starter s;
   enum TP_maincourse m;
   enum TP_dessert d;
};
Run Code Online (Sandbox Code Playgroud)

所以课程struct看起来像这样:

struct S_course {
   enum TP_course type;
   union U_subtype stype;
   float price_in_USD;
   int availability;
   ...and all the rest of data would follow...
};
Run Code Online (Sandbox Code Playgroud)

好的,一切都清楚了,但是......我可以遵循任何编码策略来尝试强制安全访问stype上面标记的联合吗?也许以某种方式使它不透明?

例如,如果我switch/case为an写了一个block,enum而忘记case为一个值写a ,编译器就会触发一个警告,这对以后维护代码有很大的帮助。但是如果我在stype.s没有先检查 if 的情况下访问type==STARTER,编译器就不能足够聪明来实现有风险的编码,并且根本不会发出警告。

我能否以某种方式组织代码,以便无法访问U_subtype工会成员,除非在一个非常有限的地方,我清楚地记录了必须如何访问这些成员?

ces*_*sss 5

经过深思熟虑后,我选择了一种方法,可以将其视为除了 PSkocik 建议的其他三种选择之外的第四种选择:重新设计,struct以便没有类型和子类型,而只有子类型。那么该类型不是由 提供的struct,而是由辅助函数提供的。

像这样的东西:

enum TP_course {STARTER, MAINCOURSE, DESSERT};
enum TP_subtype {SALAD, GRILLEDVEGETABLES, PASTA, 
                 BEEF, LAMB, FISH, APPLEPIE, ICECREAM, MOUSSE};

struct S_course {
   enum TP_subtype stype;
   float price_in_USD;
   int availability;
   /*...*/
};

enum TP_course getCourse(struct S_course *c) {
switch(c->stype) {
   case SALAD:
   case GRILLEDVEGETABLES:
   case PASTA:
      return STARTER;
   case BEEF:
   case LAMB:
   case FISH:
      return MAINCOURSE;
   case APPLEPIE:
   case ICE-CREAM:
   case MOUSSE:
      return DESSERT;
   }
}
Run Code Online (Sandbox Code Playgroud)

这种设计保证了对struct. 它可以防止您留下struct未定义的行为(例如,将类型设置为 STARTER 但忘记相应地设置子类型),并且还可以防止您读取(和写入)union不是当前成员的成员。

我倾向于更喜欢这种设计风格,并且我承认我受到了 Apple UI 指南的影响:创建一种设计,防止用户输入不支持/未定义的数据;当数据可以集中在一个地方时,切勿将数据分散到不同的地方;从设计中避免荒谬/非法的数据状态,这样您就不需要检查数据是否合法:它总是合法的;每当您可以对单个一般情况执行相同操作时,请避免特殊情况;等等,等等,等等……


PSk*_*cik 4

你可以

  1. 隐藏整个结构并公开作用于指针的访问器函数

/* header */
struct S_course; //forward declaration
enum TP_starter {SALAD, GRILLEDVEGETABLES, PASTA};
enum TP_maincourse {BEEF, LAMB, FISH};
enum TP_dessert {APPLEPIE, ICECREAM, MOUSSE};
void S_course__set_starter(struct S_course *this,  enum TP_starter starter);

//accessor functions
void S_course__set_maincourse(struct S_course *this,  enum TP_maincourse maincourse);
void S_course__set_dessert(struct S_course *this,  enum TP_dessert dessert);


/* c file */
enum TP_course {STARTER, MAINCOURSE, DESSERT};
union U_subtype {
   enum TP_starter s;
   enum TP_maincourse m;
   enum TP_dessert d;
};
struct S_course {
   enum TP_course type;
   union U_subtype stype;
   float price_in_USD;
   int availability;
   /*...*/
};
void S_course__set_starter(struct S_course *this,  enum TP_starter starter)
{
    this->type = STARTER;
    this->stype.s = starter;
}
Run Code Online (Sandbox Code Playgroud)
  1. 使用尖叫着“别碰我”的成员名称,或者类似的名称tagged_union,这样可以清楚地表明需要如何访问它。

    或者

  2. 切换到 C++ 并使用其访问控制功能(私有/受保护)仅隐藏某些成员,同时允许通过公共成员/友元函数进行访问