• No results found

Part II: Core CORBA

Chapter 4. The OMG Interface Definition Language

4.7 User-Defined Types

4.7.3 Structures

IDL supports structures containing one or more named members of arbitrary type, including user-defined complex types. For example:

struct TimeOfDay { short hour; short minute; short second; };

As in C++, this definition introduces a new type called TimeOfDay. Structure definitions form a namespace, so the names of the structure members need to be unique only within their enclosing structure. The following is legal (if ugly) IDL:

struct Outer {

struct FirstNested { long first; long second;

} first; struct SecondNested { long first; long second; } second; };

The example demonstrates that the various first and second identifiers do not cause a name collision. However, such in-line definition of types is hard to read, so the preceding is better expressed as follows:

struct FirstNested { long first; long second; }; struct SecondNested { long first; long second; }; struct Outer { FirstNested first; SecondNested second; };

Note that this definition is much more readable but is not exactly equivalent to the previous example. The nested version adds only the single type name Outer to the global namespace, whereas the non-nested version also adds FirstNested and

SecondNested.

Of course, this definition still must be considered bad style because it ruthlessly reuses the identifiers first and second for different purposes. In the interest of clarity, you should avoid such reuse even though it is legal.

4.7.4 Unions

IDL unions differ quite a bit from their C++ counterparts. In particular, they must be discriminated; they allow multiple case labels for a single union member; and they support an optional default case:

union ColorCount switch (Color) { case red:

case green: case blue:

unsigned long num_in_stock; case black:

float discount; default:

string order_details; };

The semantics of unions are the same as in C++. Only one member of the union is active at a time. However, IDL adds a discriminator (similar to a Pascal variant record) that indicates which member is currently active. In this example, num_in_stock is active when the discriminator value is red, green, or blue, and discount is active when the discriminator value is black. Any other discriminator value indicates that

order_details is active.

Union members can be of any type, including user-defined complex types. The discriminator type must be an integral type (char, an integer type, boolean, or an enumeration type). You cannot use octet as a union discriminator type.

As in C++, unions create a namespace, so union member names need be unique only within the enclosing union.

The default case of a union is optional. However, if it is present, there must be at least one unused explicit case label in the range of discriminator values; otherwise, the union is illegal, as in the following example:

union U switch (boolean) { case FALSE:

long count; case TRUE:

string message;

default: // Illegal, default case cannot happen float cost;

};

The compiler rejects this because there is no value left over that could ever activate the default member of the union.

The usual caveat for unions also applies to IDL: any attempt to interpret a value as a type other than the type of the active member results in undefined behavior. Unions are not meant to be used as a backdoor mechanism for type casting, so if you insist on interpreting a float value as a string, you will likely end up with a core dump.

We recommend that you never use the default case for unions. In addition, you should never use more than one case label per member. As you will see in Section 6.16, this practice substantially simplifies use of the generated C++ code for unions.

One particular use of IDL unions has become idiomatic and deserves special mention:

union AgeOpt switch (boolean) { case TRUE:

unsigned short age; };

Unions such as this one are used to implement optional values. A value of type AgeOpt

contains an age only if the discriminator is TRUE. If the discriminator value is FALSE, the union is empty and contains no value other than the discriminator itself.

IDL does not support optional or defaulted parameters, so the preceding union construct is frequently used to simulate that functionality. This is particularly useful if no special sentinel ("dummy") value is available to indicate the "this value is absent" condition for a parameter.

You should exercise caution before deciding to use unions in your IDL. In some cases, unions are a good way to express the desired semantics and provide better static type safety than type any. However, unions are frequently used to simulate overloading. By passing a union with several members as a parameter, you can achieve with a single operation what would otherwise require several separate operations. For example:

enum InfoKind { text, numeric, none }; union Info switch (InfoKind) {

case text: string description; case numeric: long index; }; interface Order {

void set_details(in Info details); };

With this definition, the operation set_details can do triple duty and accept parameters of type string or long or (conceptually) accept no parameter at all to clear the details stored by an Order object. Although this looks attractive at first, the client must supply a correctly initialized union parameter to the operation, something that is more complex and error-prone than passing a simple value. The following approach is simpler and easier to understand:

interface Order {

void set_text_details(in string details); void set_details_index(in long index); void clear_details();

};

This definition is semantically equivalent to the earlier one but abandons the union in favor of three separate operations.

As always, you must exercise judgment when designing your interfaces. If you are tempted to use a union, double-check to see whether there is a simpler or more elegant solution. Too often, unions are abused to create operations that are like Swiss army knives. Typically, it is better to have several operations, each operation doing exactly one thing, than to have a single operation that does many different things. If you compare the

preceding definitions, you will probably agree that the second one, which avoids the union, is much easier to understand.

4.7.5 Arrays

IDL supports both single- and multidimensional arrays of arbitrary element type. For example:

typedef Color ColorVector[10]; typedef string IDtable[10][20];

As in C++, the array bounds must be positive constant integer expressions. You must use a typedef to declare array types. The following declaration is syntactically invalid:

Color ColorVector[10]; // Invalid IDL, missing typedef

All array dimensions must be specified. IDL does not support open arrays because IDL does not support pointers. (In C and C++, open arrays are just pointers in disguise.) The following is illegal:

typedef string IDtable[][20]; // Error, open arrays are illegal

An array type definition determines the number of elements of an array, but IDL does not specify how arrays are to be indexed in different implementation languages. This means that you cannot portably send an array index from a client to a server and expect the server to interpret the index correctly. For example, the client may be written in C++, in which arrays are indexed starting at 0, but the server may be written in a different language, which may start array indexes at 1.

To portably pass array indexes across implementations, you must create a convention that determines the logical origin for indexes. For example, you can use the convention that arrays are indexed starting at 0. Clients and servers then are responsible for converting between the logical index (using a 0 origin) and the actual index value used by their respective implementation languages.

In practice, non-portable use of array indexes rarely causes a problem because it is easier and more intuitive to send the array element itself instead of its index.