为什么这个联盟显然持有不止一个价值?

Why is this union apparently holding more than one value?

我刚开始使用联合体,对这个测试如何通过感到困惑,SDL_Event 是一个联合体:

TEST(basic_check, test_eq) {
  Dot dot;

  SDL_Event event;            // this is a union, see below
  event.type = SDL_KEYDOWN;   // <= I use one member here

  SDL_Keysym keysym;          // this is a struct
  keysym.sym = SDLK_UP;
  event.key.keysym = keysym;  // <= I use another member here

  dot.handleEvent(event);     // <= but this function accesses value of the first member

  EXPECT_EQ(-Dot::DOT_VEL, dot.getVelY());
}

我的理解是联合只能持有一个值。

然而,在这个测试中,我为 event.type 设置了一个值,工会的一个成员;然后我更新 event.key,工会的另一个成员。更准确地说 event.key 是一个结构,我更新它的成员有一个结构 SDL_Keysym.

这里是随后调用的函数的代码:

void Dot::handleEvent(SDL_Event& e) {

  if (e.type == SDL_KEYDOWN && e.key.repeat == 0) { //<== access two alternate members?
    switch (e.key.keysym.sym) {   
      case SDLK_UP: 
        velY -= DOT_VEL;
        break;
      case SDLK_DOWN:
        ...   // followed by a lot of other cases 
    }
  }
}

我很困惑,因为 if 条件访问联合的两个成员(见上面的评论)。我以为他们会是排他性的。

供参考,SDL_EventSDL_KeyboardEvent定义如下:

typedef union SDL_Event
{
    Uint32 type;                    /**< Event type, shared with all events */
    SDL_CommonEvent common;         /**< Common event data */
    SDL_WindowEvent window;         /**< Window event data */
    SDL_KeyboardEvent key;          /**< Keyboard event data */
    ...            // and a long list of other events 
    ...
} SDL_Event;


typedef struct SDL_KeyboardEvent
{
    Uint32 type;        /**< ::SDL_KEYDOWN or ::SDL_KEYUP */
    Uint32 timestamp;
    Uint32 windowID;    /**< The window with keyboard focus, if any */
    Uint8 state;        /**< ::SDL_PRESSED or ::SDL_RELEASED */
    Uint8 repeat;       /**< Non-zero if this is a key repeat */
    Uint8 padding2;
    Uint8 padding3;
    SDL_Keysym keysym;  /**< The key that was pressed or released */
} SDL_KeyboardEvent;

关于在任何给定时间最多只有一名活跃成员的工会,你是对的。

但是标准做了保证,为了方便union的使用(特别是找出,像这里,哪个是active元素):

9.5/1: (...) If a standard-layout union contains several standard-layout structs that share a common initial sequence, and if an object of this standard-layout union type contains one of the standard-layout structs, it is permitted to inspect the common initial sequence of any of standard-layout struct members;

在您的示例中,SDL_Event 联合有一个联合成员 Uint32 type,所有 SDL_XXXEvent 结构也以 Uint32 开头。这是通用的初始序列,因此可以使用任何成员对其进行检查(最简单的就是 type)。

编辑:有趣的评论(从评论中接管)

正如您所指出的,测试不仅是检查:它还使用 event.typetype 中写入,然后在 event.key 中分配 keysym .因此,您想知道活动成员的更改(从 typekey)是否会使公共初始序列无效。

请放心,这绝对有效。 C++ 检查保证确保在 event.type(公共初始序列)赋值后,event.key.type 也是 SDL_KEYDOWN。当您仅更改 event.key.keysim 时,没有理由更改 type 的值。

但是请注意 timestampWindowsIDevent.key 的其他成员处于未定义状态。您的测试不使用它们,因此没有理由失败。但是为了避免这种潜在的问题,更好的方法是构造一个 SDL_KeyboardEvent,正确初始化它并将整个结构复制到 event.key

如果您仔细观察 SDL_Event,您会发现它主要是 结构的联合体,其中每个结构都具有相同的初始签名(具有单个 8-位无符号整数作为第一个成员)。

是它起作用的原因。即使联合中的结构具有不同的大小,所有结构都将具有与第一个成员相同的成员 type

这是模拟 继承 的简单方法,这意味着 SDL_Event 联合中的所有结构都是彼此的兄弟姐妹。

此外,C 规范明确允许使用联合将多种类型表示为一种 type punning


但是... 由于问题被标记为 C++ 问题,这在技术上是 未定义的行为.

为了与 C 向后兼容,大多数(如果不是全部)C++ 编译器将允许这样做而不会出现任何抱怨。