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

时间:2017-03-16 20:43:21

标签: c unions

假设你正在写一个 C true代表一顿饭的课程。课程struct中的一个字段是:

struct

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

enum TP_course {STARTER, MAINCOURSE, DESSERT};

鉴于一次只使用其中一个这样的枚举(取决于课程类型),在enum TP_starter {SALAD, GRILLEDVEGETABLES, PASTA}; enum TP_maincourse {BEEF, LAMB, FISH}; enum TP_dessert {APPLEPIE, ICECREAM, MOUSSE}; 中汇总它们是有意义的:

union

所以课程union U_subtype { enum TP_starter s; enum TP_maincourse m; enum TP_dessert d; }; 看起来像这样:

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... }; 标记的联合?也许以某种方式使它变得不透明?

例如,如果我为stype编写switch/case块而忘记为值编写enum,编译器将触发警告,这有很大帮助用于维护未来的代码。但是如果我在没有先检查case的情况下访问stype.s,编译器就不能足够聪明地实现风险编码,并且根本不会发出警告。

我是否可以通过某种方式组织代码,以便无法访问type==STARTER联盟的成员,除非在非常有限的地方我清楚地记录了对这些成员的访问权限做什么?

2 个答案:

答案 0 :(得分:2)

你可以

  1. 隐藏整个结构并公开作用于指针的访问器函数
  2. /* 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;
    }
    
    1. 使用尖叫的成员名称不要触摸我,或使用tagged_union之类的名称,这样可以明确地显示它是如何被访问的。

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

答案 1 :(得分:0)

在考虑了很多这个之后,我选择了一种方法,除了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;
   }
}

此设计保证对struct类型的安全读/写访问。它可以防止您将struct置于未定义的行为中(例如,将类型设置为STARTER但忘记相应地设置子类型),这也会阻止您阅读(和写入)union成员它不是现在的那个。

我倾向于喜欢这种设计风格,我承认我从Apple UI指南中获得了这种影响:创建一种设计,防止用户输入不支持/未定义的数据;当它可以在一个地方时,不要在不同的地方传播数据;从设计中避免荒谬/非法的数据状态,这样你就不需要检查数据是否合法:它始终是;避免特殊情况,只要你能用同一个案例做同样的事情;等,等等......