7

I have a question about something I still don't understand about unions. I've read about a lot of their uses and for the most part can see how they can be useful and understand them. I've seen that they can provide a primitive "C style" polymorphism. The example of this that I have seen on a couple websites is SDL's event union:

typedef union {
 Uint8 type;
 SDL_ActiveEvent active;
 SDL_KeyboardEvent key;
 SDL_MouseMotionEvent motion;
 SDL_MouseButtonEvent button;
 SDL_JoyAxisEvent jaxis;
 SDL_JoyBallEvent jball;
 SDL_JoyHatEvent jhat;
 SDL_JoyButtonEvent jbutton;
     SDL_ResizeEvent resize;
 SDL_ExposeEvent expose;
 SDL_QuitEvent quit;
 SDL_UserEvent user;
     SDL_SysWMEvent syswm;
} SDL_Event;

What I cannot understand is how there can be a "type" member up there coexisting with the event types? Aren't these each only allowed to exist one at a time since they occupy the same area of memory? Wouldn't the union exist at any time as EITHER a type or one of the events?

I understand that each event is actually a struct with a type member, for example:

// SDL_MouseButtonEvent

typedef struct{
     Uint8 type;
     Uint8 button;
     Uint8 state;
     Uint16 x, y;
} SDL_MouseButtonEvent;

How does this somehow make sense? Does this somehow allow the type member of the union represent the type of whatever struct the union is currently? Is this some sort of bizarre effect that happens when every member of the union except one is a struct and each struct contains that one member?

Can you access struct members without knowing which struct the object is?

Thanks!

user3386109
  • 34,287
  • 7
  • 49
  • 68
Russel
  • 2,335
  • 3
  • 19
  • 11
  • Please verify that the `Uint8 type` field is related to the other fields. I believe that the `type` field has nothing to do with the other fields. It is just another field. – Thomas Matthews Feb 09 '11 at 01:00

6 Answers6

9

If each of the event types has a Uint8 as its first data member, then the type member of the union is just a convenience.

The general rule with unions is that you can only access the data in the union using the last data member to which you wrote. So, if you last wrote to active, you couldn't next read from key.

An exception to this rule is that if several members of a union share the same prefix (if their first data member(s) are the same), you can access that prefix via any of the data members of the union that share the prefix. So, here, you could refer to active.type or key.type, regardless of which data member of the union is active, and it would work.

The type member of SDL_Event is just a convenient shortcut that allows you to access that type field without having to qualify it as event_object.active.type or event_object.key.type. You can just use event_object.type.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • +1, neat, second useful thing I've learnt today... I guess it's obvious when you think about it, but sometimes it's the obvious things that I miss... :) – Nim Feb 09 '11 at 00:20
  • 2
    I still find this construct unusual, I have seen before things like: `struct SDLEvent { Uint8 type; union { type1 t1; type2 t2... } };` – David Rodríguez - dribeas Feb 09 '11 at 00:23
  • @David: I too think this is a bit unusual; I was going to suggest that approach, but was then wondering whether there is some benefit to the approach taken by SDL. I can't think of any benefit, but I really am not an expert on unions; I've never really used them much. Maybe it's just a quirky design choice... – James McNellis Feb 09 '11 at 00:24
  • For one thing, the way SDL does it allows direct C-style casts both ways between `SDLEvent*` and `SDLActiveEvent*`, etc. (The other way would too, but with more typing.) – aschepler Feb 09 '11 at 00:29
  • @James: Interesting! But how many members qualify as "several" (with respect to "several members of a union share the same prefix"). – Jacob Feb 09 '11 at 01:13
  • @Jacob: As many members as there are that are the same. It's the entire "common initial sequence" of data members that are shared. – James McNellis Feb 09 '11 at 01:37
  • @James Whether it is unusual depends on how many such cases you have seen, or for that matter written; from my 30+ year career writing C, I would have called it "not unusual". An advantage is that you can malloc an SDL_* and set its type field without having to do weird arithmetic to get the malloc size or to cast back to the union type. If you think of polymorphic types and inheritance, this is a natural structure -- a subtype contains all of the members of its supertype. – Jim Balter Feb 09 '11 at 03:37
  • @Jim: Well, I did readily admit that I am not particularly knowledgeable about the idiomatic use of unions :-). You make an excellent point. – James McNellis Feb 09 '11 at 03:39
6

Let's look at how the union is layed out in memory:

Address:    Uint8    MouseButtonEvent   KeyboardEvent
x+0x0       type     type               type
x+0x1       -        button             ?
x+0x2       -        state              ?
...

It just so happens that the type members all line up, so regardless of what type it is, accessing the union as a Uint8 will yield the actual type of the event.

Anon.
  • 58,739
  • 8
  • 81
  • 86
0

If each of those SDL_xyz struct's first couple of bytes are their own type field, that means that when the union contains one of those objects, the union's first couple of bytes are the same first couple of bytes as the SDL struct - i.e. the type field.

The union doesn't contain both, it just contains the SDL object whose first member happens to coincide in type, size and location with the 'type' field.

leegent
  • 904
  • 6
  • 11
0

Unions in C/C++ are required by the standard to be.aligned to the strictest type they contain. Furthermore, as members of structures cannot be reordered and due to the requirements of the current standards(changing in C++0x) that unions only contain POD types, the idea is that the type member of the union maps to the type member in the struct decks it contains.

Washu
  • 817
  • 6
  • 6
0

As far as the union is concerned, there is no real difference between a struct and a primitive type. The SDL_MouseButtonEven struct is just a bunch of types one after the other.
The type member of the union takes the place of the type member of the event structs. to put it graphically, it looks like this:

SDL_Event union:
  type:    |--------|
  motion:  |--type--|-button-|--state-|-------x--------|-------y--------|  
  active:  |--type--|----------something-else-of-another-event--|          
  key:     |--type--|--maybe-a-smaller-event-|                             
  ... etc'
shoosh
  • 76,898
  • 55
  • 205
  • 325
0

[Edited, thanks to James' comment.]

The Standard makes some special guarantees on unions so that this sort of thing is safe.

The important rule is that if two POD structs in a union start with the same member types, you are allowed to use the "common initial sequence" of shared members. So if key.type was set, it's legal to read button.type and it will have the same value.

Also, the plain data member (of a fundamental type) type directly in the union must be laid out in memory as though it were in a struct containing just that member. In other words, type is also equivalent to key.type and button.type, etc.

aschepler
  • 70,891
  • 9
  • 107
  • 161
  • 3
    It is my understanding that it is fully guaranteed for POD unions because "each data member is allocated as if it were the sole member of a struct" (C++03 9.5/1). All of the data members _must_ have the same address because there cannot be unnamed padding before the first member of a struct. – James McNellis Feb 09 '11 at 00:29
  • @James Aha, thanks! I don't know where I got that conclusion, unless from reading 9.2 and missing that sentence in 9.5. – aschepler Feb 09 '11 at 00:35