[Top] [Prev] [Next] [Bottom]

Mapping of OMG IDL to C++

16


This chapter explains how OMG IDL constructs are mapped to the constructs of the C++ programming language. It provides mapping information for:

16.1 Preliminary Information

16.1.1 Scoped Names

Scoped names in OMG IDL are specified by C++ scopes:

16.1.2 C++ Type Size Requirements

The sizes of the C++ types used to represent OMG IDL types are implementation-dependent. That is, this mapping makes no requirements as to the sizeof(T) for anything except basic types (see Section 16.5, "Mapping for Basic Data Types," on page 16-10) and string (see Section 16.7, "Mapping For String Types," on page 16-11).

16.1.3 CORBA Module

The mapping relies on some predefined types, classes, and functions that are logically defined in a module named CORBA. The module is automatically accessible from a C++ compilation unit that includes a header file generated from an OMG IDL specification. In the examples presented in this document, CORBA definitions are referenced without explicit qualification for simplicity. In practice, fully scoped names or C++ using statements for the CORBA namespace would be required in the application source. Appendix A contains standard OMG IDL types.

16.2 Mapping for Modules

A shown in Section 16.1.1, "Scoped Names," on page 16-1, a module defines a scope, and as such is mapped to a C++ namespace with the same name:

// IDL
module M
{
// definitions
};
// C++
namespace M
{
// definitions
}

Because namespaces were only recently added to the C++ language, few C++ compilers currently support them. Alternative mappings for OMG IDL modules that do not require C++ namespaces are in the Appendix "Alternative Mappings for C++ Dialects."

16.3 Mapping for Interfaces

An interface is mapped to a C++ class that contains public definitions of the types, constants, operations, and exceptions defined in the interface.

A CORBA-C++-compliant program cannot

16.3.1 Object Reference Types

The use of an interface type in OMG IDL denotes an object reference. Because of the different ways an object reference can be used and the different possible implementations in C++, an object reference maps to two C++ types. For an interface A, these types are named A_var and A_ptr. For historical reasons, the type ARef is defined as a synonym for A_ptr, but usage of the Ref names is deprecated. These types need not be distinct-A_var may be identical to A_ptr, for example-so a compliant program cannot overload operations using these types solely.

An operation can be performed on an object by using an arrow ("->") on a reference to the object. For example, if an interface defines an operation op with no parameters and obj is a reference to the interface type, then a call would be written obj->op(). The arrow operator is used to invoke operations on both the _ptr and _var object reference types.

Client code frequently will use the object reference variable type (A_var) because a variable will automatically release its object reference when it is deallocated or when assigned a new object reference. The pointer type (A_ptr) provides a more primitive object reference, which has similar semantics to a C++ pointer. Indeed, an implementation may choose to define A_ptr as A*, but is not required to. Unlike C++ pointers, however, conversion to void*, arithmetic operations, and relational operations, including test for equality, are all non-coompliant. A compliant implementation need not detect these incorrect uses because requiring detection is not practical.

For many operations, mixing data of type A_var and A_ptr is possible without any explicit operations or casts. However, one needs to be careful in doing so because of the implicit release performed when the variable is deallocated. For example, the assignment statement in the code below will result in the object reference held by p to be released at the end of the block containing the declaration of a.

// C++
A_var a;
A_ptr p = // ...somehow obtain an objref...
a = p;

16.3.2 Widening Object References

OMG IDL interface inheritance does not require that the corresponding C++ classes are related, though that is certainly one possible implementation. However, if interface B inherits from interface A, the following implicit widening operations for B must be supported by a compliant implementation:

16.3.3 Object Reference Operations

Conceptually, the Object class in the CORBA module is the base interface type for all CORBA objects. Any object reference can therefore be widened to the type Object_ptr. As with other interfaces, the CORBA namespace also defines the type Object_var.

CORBA defines three operations on any object reference:duplicate, release, and is_nil. Note that these are operations on the object reference, not the object implementation. Because the mapping does not require object references to themselves be C++ objects, the "->" syntax cannot be employed to express the usage of these operations. Also, for convenience these operations are allowed to be performed on a nil object reference.

The release and is_nil operations depend only on type Object, so they can be expressed as regular functions within the CORBA namespace as follows:

// C++
void release(Object_ptr obj);
Boolean is_nil(Object_ptr obj);

The release operation indicates that the caller will no longer access the reference so that associated resources may be deallocated. If the given object reference is nil, release does nothing. The is_nil operation returns TRUE if the object reference contains the special value for a nil object reference as defined by the ORB. Neither the release operation nor the is_nil operation may throw CORBA exceptions.

The duplicate operation returns a new object reference with the same static type as the given reference. The mapping for an interface therefore includes a static member function named _duplicate in the generated class. For example:

// IDL
interface A { };
// C++
class A
{
public:
static A_ptr _duplicate(A_ptr obj);
};

If the given object reference is nil, _duplicate will return a nil object reference. The _duplicate operation can throw CORBA system exceptions.

16.3.4 Narrowing Object References

The mapping for an interface defines a static member function named _narrow that returns a new object reference given an existing reference. Like _duplicate, the _narrow function returns a nil object reference if the given reference is nil. Unlike _duplicate, the parameter to _narrow is a reference of an object of any interface type (Object_ptr). If the actual (runtime) type of the parameter object can be widened to the requested interface's type, then _narrow will return a valid object reference. Otherwise, _narrow will return a nil object reference. For example, suppose A, B, C, and D are interface types, and D inherits from C, which inherits from B, which in turn inherits from A. If an object reference to a C object is widened to an A_ptr variable called ap, the

16.3.5 Nil Object Reference

The mapping for an interface defines a static member function named _nil that returns a nil object reference of that interface type. For each interface A, the following call is guaranteed to return TRUE:

// C++
Boolean true_result = is_nil(A::_nil());

A compliant application need not call release on the object reference returned from the _nil function.

As described in Section 16.3.1, object references may not be compared using operator==, so is_nil is the only compliant way an object reference can be checked to see if it is nil.

The _nil function may not throw any CORBA exceptions.

A compliant program cannot attempt to invoke an operation through a nil object reference, since a valid C++ implementation of a nil object reference is a null pointer.

16.3.6 Interface Mapping Example

The example below shows one possible mapping for an interface. Other mappings are also possible, but they must provide the same semantics and usage as this example.

// IDL
interface A
{
A op(in A param);
};
// C++
class A;
typedef A *A_ptr;
typedef A_ptr ARef;
class A : public virtual Object
{
public:
static A_ptr _duplicate(A_ptr obj);
static A_ptr _narrow(Object_ptr obj);
static A_ptr _nil();

virtual A_ptr op(A_ptr param) = 0;

protected:
A();
virtual ~A();

private:
A(const A&);
void operator=(const A&);
};

class A_var : public _var
{
public:
A_var() : ptr_(A::_nil()) {}
A_var(A_ptr p) : ptr_(p) {}
A_var(const A_var &a) : ptr_(A::_duplicate(A_ptr(a))) {}
~A_var() { free(); }

A_var &operator=(A_ptr p) {
reset(p); return *this;
}

operator const A_ptr&() const { return ptr_; }
operator A_ptr&() { return ptr_; }
A_ptr operator->() const { return ptr_; }

protected:
A_ptr ptr_;
void free() { release(ptr_); }
void reset(A_ptr p) { free(); ptr_ = p; }

private:
// hidden assignment operators for var types to
// fulfill the rules specified in
Section 16.3.2
void operator=(const A_var &);
void operator=(const _var &);
};

16.4 Mapping for Constants

OMG IDL constants are mapped directly to a C++ constant definition that may or may not define storage depending on the scope of the declaration. In the example below, a top-level IDL constant maps to a file-scope C++ constant whereas a nested constant maps to a class-scope C++ constant. This inconsistency occurs because C++ file-scope constants may not require storage (or the storage may be replicated in each compilation unit), while class-scope constants always take storage. As a side effect, this difference means that the generated C++ header file might not contain values for constants defined in the OMG IDL file.

// IDL
const string name = "testing";

interface A
{
const float pi = 3.14159;
};
// C++
static const char *const name = "testing";

class A
{
public:
static const Float pi;
};

In certain situations, use of a constant in OMG IDL must generate the constant's value instead of the constant's name.3 For example,

// IDL
interface A
{
const long n = 10;
typedef long V[n];
};
// C++
class A
{
public:
static const long n;
typedef long V[10];
};

16.5 Mapping for Basic Data Types

The basic data types have the mappings shown in Table 23 on page 16-10. Note that the mapping of the OMG IDL boolean type defines only the values 1 (TRUE) and 0 (FALSE); other values produce undefined behavior.
TABLE 23. Basic Data Type Mappings

OMG IDL

C++

short

CORBA::Short

long

CORBA::Long

unsigned short

CORBA::UShort

unsigned long

CORBA::ULong

float

CORBA::Float

double

CORBA::Double

char

CORBA::Char

boolean

CORBA::Boolean

octet

CORBA::Octet

Each OMG IDL basic type is mapped to a typedef in the CORBA module. This is because some types, such as short and long, may have different representations on different platforms, and the CORBA definitions will reflect the appropriate representation. For example, on a 64-bit machine where a long integer is 64 bits, the definition of CORBA::Long would still refer to a 32-bit integer. Requirements for the sizes of basic types are shown in Section 3.8.1, "Basic Types," on page 3-20.

Except for boolean, char, and octet, the mappings for basic types must be distinguishable from each other for the purposes of overloading. That is, one can safely write overloaded C++ functions on Short, UShort, Long, ULong, Float, and Double.

Programmers concerned with portability should use the CORBA types. However, some may feel that using these types with the CORBA qualification impairs readability. If the CORBA module is mapped to a namespace, a C++ using statement may help this problem. On platforms where the C++ data type is guaranteed to be identical to the OMG IDL data type, a compliant implementation therefore may generate the native C++ type.

For the Boolean type, only the values 1 (representing TRUE) and 0 (representing FALSE) are defined; other values produce undefined behavior. Since many existing C++ software packages and libraries already provide their own preprocessor macro definitions of TRUE and FALSE, this mapping does not require that such definitions be provided by a compliant implementation. Requiring definitions for TRUE and FALSE could cause compilation problems for CORBA applications that make use of such packages and libraries. Instead, we recommend that compliant applications simply use the values 1 and 0 directly.4 Alternatively, for those C++ compilers that support the new bool type, the keywords true and false may be used.

16.6 Mapping for Enums

An OMG IDL enum maps directly to the corresponding C++ type definition. The only difference is that the generated C++ type may need an additional constant that is large enough to force the C++ compiler to use exactly 32 bits for values declared to be of the enumerated type.

// IDL
enum Color { red, green, blue };
// C++
enum Color { red, green, blue };

16.7 Mapping For String Types

As in the C mapping, the OMG IDL string type, whether bounded or unbounded, is mapped to char* in C++. String data is null-terminated. In addition, the CORBA module defines a class String_var that contains a char* value and automatically frees the pointer when a String_var object is deallocated. When a String_var is constructed or assigned from a char*, the char* is consumed and thus the string data may no longer be accessed through it by the caller. Assignment or construction from a const char* or from another String_var causes a copy. The String_var class also provides operations to convert to and from char* values, as well as subscripting operations to access characters within the string. The full definition of the String_var interface is given in "String_var Class" on page C-2.

Because its mapping is char*, the OMG IDL string type is the only non-basic type for which this mapping makes size requirements.

For dynamic allocation of strings, compliant programs must use the following functions from the CORBA namespace:

// C++
namespace CORBA {
char *string_alloc(ULong len);
char *string_dup(const char*);
void string_free(char *);
...
}

The string_alloc function dynamically allocates a string, or returns a null pointer if it cannot perform the allocation. It allocates len+1 characters so that the resulting string has enough space to hold a trailing NUL character. The string_dup function dynamically allocates enough space to hold a copy of its string argument, including the NUL character, copies its string argument into that memory, and returns a pointer to the new string. If allocation fails, a null pointer is returned. The string_free function deallocates a string that was allocated with string_alloc or string_dup. Passing a null pointer to string_free is acceptable and results in no action being performed. These functions allow ORB implementations to use special memory management mechanisms for strings if necessary, without forcing them to replace global operator new and operator new[].

The string_alloc, string_dup, and string_free functions may not throw CORBA exceptions.

Note that a static array of char in C++ decays to a char*, so care must be taken when assigning one to a String_var, since the String_var will assume the pointer points to data allocated via string_alloc and thus will eventually attempt to string_free it:

// C++
// The following is an error, since the char* should point to
// data allocated via string_alloc so it can be consumed
String_var s = "static string"; // error

// The following are OK, since const char* are copied,
// not consume
const char* sp = "static string";
s = sp;
s = (const char*)"static string too";

16.8 Mapping for Structured Types

The mapping for struct, union, and sequence (but not array) is a C++ struct or class with a default constructor, a copy constructor, an assignment operator, and a destructor. The default constructor initializes object reference members to appropriately-typed nil object references and string members to NULL; all other members are initialized via their default constructors. The copy constructor performs a deep-copy from the existing structure to create a new structure, including calling _duplicate on all object reference members and performing the necessary heap allocations for all string members. The assignment operator first releases all object reference members and frees all string members, and then performs a deep-copy to create a new structure. The destructor releases all object reference memebrs and frees all string members.

The mapping for OMG IDL structured types (structs, unions, arrays, and sequences) can vary slightly depending on whether the data structure is fixed-length or variable-length. A type is variable-length if it is one of the following types:

16.8.1 T_var Types

The general form of the T_var types is shown below.

// C++
class T_var
{
public:
T_var();
T_var(T *);
T_var(const T_var &);
~T_var();

T_var &operator=(T *);
T_var &operator=(const T_var &);

T *operator-> const ();
// other conversion operators to support
// parameter passing
};

The default constructor creates a T_var containing a null T*. Compliant applications may not attempt to convert a T_var created with the default constructor into a T* nor use its overloaded operator-> without first assigning to it a valid T* or another valid T_var. Due to the difficulty of doing so, compliant implementations are not required to detect this error. Conversion of a null T_var to a T*& is allowed, however, so that a T_var can legally be passed as an out parameter.

The T* constructor creates a T_var that, when destroyed, will delete the storage pointed to by the T* parameter. The parameter to this constructor should never be a null pointer. Compliant implementations are not required to detect null pointers passed to this constructor.

The copy constructor deep-copies any data pointed to by the T_var constructor parameter. This copy will be destroyed when the T_var is destroyed or when a new value is assigned to it. Compliant implementations may, but are not required to, utilize some form of reference counting to avoid such copies.

The destructor uses delete to deallocate any data pointed to by the T_var, except for strings and array types, which are deallocated using the string_free and T_free (for array type T) deallocation functions, respectively.

The T* assignment operator results in the deallocation of any old data pointed to by the T_var before assuming ownership of the T* parameter.

The normal assignment operator deep-copies any data pointed to by the T_var assignment parameter. This copy will be destroyed when the T_var is destroyed or when a new value is assigned to it.

The overloaded operator-> returns the T* held by the T_var, but retains ownership of it. Coompliant applications may not call this function unless the T_var has been initialized with a valid T* or T_var.

In addition to the member functions described above, the T_var types must support conversion functions that allow them to fully support the parameter passing modes shown in Table 24 on page 16-44. The form of these conversion functions is not specified so as to allow different implementations, but the conversions must be automatic (i.e., they must require no explicit application code to invoke them).

The T_var types are also produced for fixed-length structured types for reasons of consistency. These types have the same semantics as T_var types for variable-length types. This allows applications to be coded in terms of T_var types regardless of whether the underlying types are fixed- or variable-length.

Each T_var type must be defined at the same level of nesting as its T type.

T_var types do not work with a pointer to constant T, since they provide no constructor nor operator= taking a const T* parameter. Since C++ does not allow delete to be called on a const T*, the T_var object would normally have to copy the const object; instead, the absence of the const T* constructor and assignment operators will result in a compile-time error if such an initialization or assignment is attempted. This allows the application developer to decide if a copy is really wanted or not. Explicit copying of const T* objects into T_var types can be achieved via the copy constructor for T:

// C++
const T *t = ...;
T_var tv = new T(*t);

16.9 Mapping for Struct Types

An OMG IDL struct maps to C++ struct, with each OMG IDL struct member mapped to a corresponding member of the C++ struct. This mapping allows simple field access as well as aggregate initialization of most fixed-length structs. To facilitate such initialization, C++ structs must not have user-defined constructors, assignment operators, or destructors, and each struct member must be of self-managed type. With the exception of strings and object references, the type of a C++ struct member is the normal mapping of the OMG IDL member's type.

For a string or object reference member, the name of the C++ member's type is not specified by the mapping-a compliant program therefore cannot create an object of that type. The behavior6 of the type is the same as the normal mapping (char* for string, A_ptr for an interface A) except the type's copy constructor copies the member's storage and its assignment operator releases the member's old storage.

Assignment between a string or object reference member and a corresponding T_var type (String_var or A_var) always results in copying the data, while assignment with a pointer does not. The one exception to the rule for assignment is when a const char* is assigned to a member, in which case the storage is copied.

When the old storage must not be freed (for example, it is part of the function's activation record), one can access the member directly as a pointer using the _ptr field accessor. This usage is dangerous and generally should be avoided.

// IDL
struct Fixed{ float x, y, z; };
// C++
Fixed x1 = {1.2, 2.4, 3.6};
Fixed_var x2 = new Fixed;
x2->y = x1.z;

The example above shows usage of the T and T_var types for a fixed-length struct. When it goes out of scope, x2 will automatically free the heap-allocated Fixed object using delete.

The following examples illustrate mixed usage of T and T_var types for variable-length types, using the following OMG IDL definition:

// IDL
interface A;
struct Variable { string name; };
// C++
Variable str1; // str1.name is initially NULL
Variable_var str2 = new Variable; // str2->name is initially NULL
char *non_const;
const char *const2;
String_var string_var;
const char *const3 = "string 1";
const char *const4 = "string 2";

str1.name = const3; // 1: free old storage, copy
str2->name = const4; // 2: free old storage, copy

In the example above, the name components of variables str1 and str2 both start out as null. On the line marked 1, const3 is assigned to the name component of str1; this results in the previous str1.name being freed, and since const3 points to const data, the contents of const3 being copied. In this case, str1.name started out as null, so no previous data needs to be freed before the copying of const3 takes place. Line 2 is similar to line 1, except that str2 is a T_var type.

Continuing with the example:

// C++
non_const = str1.name; // 3: no free, no copy
const2 = str2->name; // 4: no free, no copy

On the line marked 3, str1.name is assigned to non_const. Since non_const is a pointer type (char*), str1.name is not freed, nor are the data it points to copied. After the assignment, str1.name and non_const effectively point to the same storage, with str1.name retaining ownership of that storage. Line 4 is identical to line 3, even though const2 is a pointer to const char; str2->name is neither freed nor copied because const2 is a pointer type.

// C++
str1.name = non_const; // 5: free, no copy
str1.name = const2; // 6: free old storage, copy

Line 5 involves assignment of a char* to str1.name, which results in the old str1.name being freed and the value of the non_const pointer, but not the data it points to, being copied. In other words, after the assignment str1.name points to the same storage as non_const points to. Line 6 is the same as line 5 except that because const2 is a const char*, the data it points to are copied.

// C++
str2->name = str1.name; // 7: free old storage, copy
str1.name = string_var; // 8: free old storage, copy
string_var = str2->name; // 9: free old storage, copy

On line 7, assignment is performed to a member from another member, so the original value is of the left-hand member is freed and the new value is copied. Similarly, lines 8 and 9 involve assignment to or from a String_var, so in both cases the original value of the left-hand side is freed and the new value is copied.

// C++
str1.name._ptr = str2.name; // 10: no free, no copy

Finally, line 10 uses the _ptr field accessor, so no freeing or copying takes place. Such usage is dangerous and generally should be avoided.

ORB implementations concerned with single-process interoperability with the C mapping may overload operator new() and operator delete() for structs so that dynamic allocation uses the same mechanism as the C language dynamic allocation functions. Whether these operators are overloaded by the implementation or not, compliant programs use new to dynamically allocate structs and delete to free them.

16.10 Mapping for Union Types

Unions map to C++ classes with access functions for the union members and discriminant. The default union constructor performs no application-visible initialization of the union. It does not initialize the discriminator, nor does it initialize any union members to a state useful to an application. (The implementation of the default constructor can do whatever type of initialization it wants to, but such initialization is implementation-dependent. No compliant application can count on a union ever being properly initialized by the default constructor alone.)

It is therefore an error for an application to access the union before setting it, but ORB implementations are not required to detect this error due to the difficulty of doing so. The copy constructor and assignment operator both perform a deep-copy of their parameters, with the assignment operator releasing old storage if necessary. The destructor releases all storage owned by the union.

The union discriminant access functions have the name _d to both be brief and avoid name conflicts with the members. The _d discriminator modifier function can only be used to set the discriminant to a value within the same union member. In addition to the _d accessors, a union with an implicit default member provides a _default() member function that sets the discriminant to a legal default value. A union has an implicit default member if it does not have a default case and not all permissible values of the union discriminant are listed.

Setting the union value through an access function automatically sets the discriminant and may release the storage associated with the previous value. Attempting to get a value through an access function that does not match the current discriminant results in undefined behavior. If an access function for a union member with multiple legal discriminant values is used to set the value of the discriminant, the union implementation is free to set the discriminant to any one of the legal values for that member. The actual discriminant value chosen under these circumstances is implementation dependent.

The following example helps illustrate the mapping for union types:

// IDL
typedef octet Bytes[64];
struct S { long len; };
interface A;
union U switch (long) {
case 1: long x;
case 2: Bytes y;
case 3: string z;
case 4:
case 5: S w;
default: A obj;
};
// C++
typedef Octet Bytes[64];
typedef Octet Bytes_slice;
class Bytes_forany { ... };
struct S { Long len; };
typedef ... A_ptr;
class U
{
public:
U();
U(const U&);
~U();
U &operator=(const U&);

void _d(Long);
Long _d() const;

void x(Long);
Long x() const;

void y(Bytes);
Bytes_slice *y() const;

void z(char*); // free old storage, no copy
void z(const char*); // free old storage, copy
void z(const String_var &); // free old storage, copy
const char *z() const;

void w(const S &); // deep copy
const S &w() const; // read-only access
S &w(); // read-write access

void obj(A_ptr); // release old objref, duplicate
A_ptr obj() const; // no duplicate
};

Accessor and modifier functions for union members provide semantics similar to that of struct data members. Modifier functions perform the equivalent of a deep-copy of their parameters, and their parameters should be passed by value (for small types) or by reference to const (for larger types). Accessors that return a reference to a non-const object can be used for read-write access, but such accessors are only provided for the following types: struct, union, sequence, and any.

For an array union member, the accessor returns a pointer to the array slice, where the slice is an array with all dimensions of the original except the first (array slices are described in detail in Section 16.12). The array slice return type allows for read-write access for array members via regular subscript operators. For members of an anonymous array type, supporting typedefs for the array must be generated directly into the union. For example:

// IDL
union U switch (long) {
default: long array[20][20];
};
// C++
class U
{
public:
// ...
void array(long arg[20][20]);
typedef long _array_slice[20];
_array_slice * array();
// ...
};

The name of the supporting array slice typedef is created by prepending an underscore and appending "_slice" to the union member name. In the example above, the array member named "array" results in an array slice typedef called "_array_slice" nested in the union class.

For string union members, the char* modifier results in the freeing of old storage before ownership of the pointer parameter is assumed, while the const char* modifier and the String_var modifier7 both result in the freeing of old storage before the parameter's storage is copied. The accessor for a string member returns a const char* to allow examination but not modification of the string storage.8

For object reference union members, object reference parameters to modifier functions are duplicated after the old object reference is released. An object reference return value from an accessor function is not duplicated because the union retains ownership of the object reference.

The restrictions for using the _d discriminator modifier function are shown by the following examples, based on the definition of the union U shown above:

// C++
S s = {10};
U u;
u.w(s); // member w selected
u._d(4); // OK, member w selected
u._d(5); // OK, member w selected
u._d(1); // error, different member selected
A_ptr a = ...;
u.obj(a); // member obj selected
u._d(7); // OK, member obj selected
u._d(1); // error, different member selected

As shown here, the _d modifier function cannot be used to implicitly switch between different union members. The following shows an example of how the _default() member function is used:

// IDL
union Z switch(boolean) {
case TRUE: short s;
};
// C++
Z z;
z._default(); // implicit default member selected
Boolean disc = z._d(); // disc == FALSE
U u; // union U from previous example
u._default(); // error, no _default() provided

For union Z, calling the _default() member function causes the union's value to be composed solely of the discriminator value of FALSE, since there is no explicit default member. For union U, calling _default() causes a compilation error because U has an expliitly declared default case and thus no _default() member function. A _default() member function is only generated for unions with implicit default members.

ORB implementations concerned with single-process interoperability with the C mapping may overload operator new() and operator delete() for unions so that dynamic allocation uses the same mechanism as the C language dynamic allocation functions. Whether these operators are overloaded by the implementation or not, compliant programs use new to dynamically allocate unions and delete to free them.

16.11 Mapping for Sequence Types

A sequence is mapped to a C++ class that behaves like an array with a current length and a maximum length. For a bounded sequence, the maximum length is implicit in the sequence's type and cannot be explicitly controlled by the programmer. For an unbounded sequence, the initial value of the maximum length can be specified in the sequence constructor to allow control over the size of the initial buffer allocation. The programmer may always explicitly modify the current length of any sequence.

For an unbounded sequence, setting the length to a larger value than the current length may reallocate the sequence data. Reallocation is conceptually equivalent to creating a new sequence of the desired new length, copying the old sequence elements zero through length-1 into the new sequence, and then assigning the old sequence to be the same as the new sequence. Setting the length to a smaller value than the current length does not affect how the storage associated with the sequence is manipulated. Note, however, that the elements orphaned by this reduction are no longer accessible and that their values cannot be recovered by increasing the sequence length to its original value.

For a bounded sequence, attempting to set the current length to a value larger than the maximum length given in the OMG IDL specification produces undefined behavior.

For each different named OMG IDL sequence type, a compliant implementation provides a separate C++ sequence type. For example:

// IDL
typedef sequence<long> LongSeq;
typedef sequence<LongSeq, 3> LongSeqSeq;
// C++
class LongSeq // unbounded sequence
{
public:
LongSeq(); // default constructor
LongSeq(ULong max); // maximum constructor
LongSeq( // T *data constructor
ULong max,
ULong length,
Long *value,
Boolean release = FALSE
);
LongSeq(const LongSeq&);
~LongSeq();
...
};

class LongSeqSeq // bounded sequence
{
public:
LongSeqSeq(); // default constructor
LongSeqSeq( // T *data constructor
ULong length,
LongSeq *value,
Boolean release = FALSE
);
LongSeqSeq(const LongSeqSeq&);
~LongSeqSeq();
...
};

For both bounded and unbounded sequences, the default constructor (as shown in the example above) sets the sequence length equal to 0. For bounded sequences, the maximum length is part of the type and cannot be set or modified, while for unbounded sequences, the default constructor also sets the maximum length to 0. The default constructor for a bounded sequence always allocates a contents vector, so it always sets the release flag to TRUE.

Unbounded sequences provide a constructor that allows only the initial value of the maximum length to be set (the "maximum constructor" shown in the example above). This allows applications to control how much buffer space is initially allocated by the sequence. This constructor also sets the length to 0 and the release flag to TRUE.

The "T *data" constructor (as shown in the example above) allows the length and contents of a bounded or unbounded sequence to be set. For unbounded sequences, it also allows the initial value of the maximum length to be set. For this constructor, ownership of the contents vector is determined by the release parameter-FALSE means the caller owns the storage, while TRUE means that the sequence assumes ownership of the storage. If release is TRUE, the contents vector must have been allocated using the sequence allocbuf function, and the sequence will pass it to freebuf when finished with it. The allocbuf and freebuf functions are described on "Additional Memory Management Functions" on page 16-25.

The copy constructor creates a new sequence with the same maximum and length as the given sequence, copies each of its current elements (items zero through length-1), and sets the release flag to TRUE.

The assignment operator deep-copies its parameter, releasing old storage if necessary. It behaves as if the original sequence is destroyed via its destructor and then the source sequence copied using the copy constructor.

If release=TRUE, the destructor destroys each of the current elements (items zero through length-1).

For an unbounded sequence, if a reallocation is necessary due to a change in the length and the sequence was created using the release=TRUE parameter in its constructor, the sequence will deallocate the old storage. If release is FALSE under these circumstances, old storage will not be freed before the reallocation is performed. After reallocation, the release flag is always set to TRUE.

For an unbounded sequence, the maximum() accessor function returns the total amount of buffer space currently available. This allows applications to know how many items they can insert into an unbounded sequence without causing a reallocation to occur. For a bounded sequence, maximum() always returns the bound of the sequence as given in its OMG IDL type declaration.

The overloaded subscript operators (operator[]) return the item at the given index. The non-const version must return something that can serve as an lvalue (i.e., something that allows assignment into the item at the given index), while the const version must allow read-only access to the item at the given index.

The overloaded subscript operators may not be used to access or modify any element beyond the current sequence length. Before either form of operator[] is used on a sequence, the length of the sequence must first be set using the length(ULong) modifier function, unless the sequence was constructed using the T *data constructor.

For strings and object references, operator[] for a sequence must return a type with the same semantics as the types used for string and object reference members of structs and arrays, so that assignment to the string or object reference sequence member via operator=() will release old storage when appropriate. Note that whatever these special return types are, they must honor the setting of the release parameter in the T *data constructor with respect to releasing old storage.

For the T *data sequence constructor, the type of T for strings and object references is char* and T_ptr, respectively. In other words, string buffers are passed as char** and object reference buffers are passed as T_ptr*.

16.11.1 Sequence Example

The example below shows full declarations for both a bounded and an unbounded sequence.

// IDL
typedef sequence<T> V1; // unbounded sequence
typedef sequence<T, 2> V2; // bounded sequence
// C++
class V1 // unbounded sequence
{
public:
V1();
V1(ULong max);
V1(ULong max, ULong length, T *data,
Boolean release = FALSE);
V1(const V1&);
~V1();
V1 &operator=(const V1&);

ULong maximum() const;

void length(ULong);
ULong length() const;

T &operator[](ULong index);
const T &operator[](ULong index) const;
};

class V2 // bounded sequence
{
public:
V2();
V2(ULong length, T *data, Boolean release = FALSE);
V2(const V2&);
~V2();
V2 &operator=(const V2&);

ULong maximum() const;

void length(ULong);
ULong length() const;

T &operator[](ULong index);
const T &operator[](ULong index) const;
};

16.11.2 Using the "release" Constructor Parameter

Consider the following example:

// IDL
typedef sequence<string, 3> StringSeq;
// C++
char *static_arr[] = {"one", "two", "three"};
char **dyn_arr = StringSeq::allocbuf(3);
dyn_arr[0] = string_dup("one");
dyn_arr[1] = string_dup("two");
dyn_arr[2] = string_dup("three");

StringSeq seq1(3, static_arr);
StringSeq seq2(3, dyn_arr, TRUE);

seq1[1] = "2"; // no free, no copy
char *str = string_dup("2");
seq2[1] = str; // free old storage, no copy

In this example, both seq1 and seq2 are constructed using user-specified data, but only seq2 is told to assume management of the user memory (because of the release=TRUE parameter in its constructor). When assignment occurs into seq1[1], the right-hand side is not copied, nor is anything freed because the sequence does not manage the user memory. When assignment occurs into seq2[1], however, the old user data must be freed before ownership of the right-hand side can be assumed, since seq2 manages the user memory. When seq2 goes out of scope, it will call string_free for each of its elements and freebuf on the buffer given to it in its constructor.

When the release flag is set to TRUE and the sequence element type is either a string or an object reference type, the sequence will individually release each element before releasing the contents buffer. It will release strings using string_free, and it will release object references using the release function from the CORBA namespace.

In general, assignment should never take place into a sequence element via operator[] unless release=TRUE due to the possibility for memory management errors. In particular, a sequence constructed with release=FALSE should never be passed as an inout parameter because the callee has no way to determine the setting of the release flag, and thus must always assume that release is set to TRUE. Code that creates a sequence with release=FALSE and then knowingly and correctly manipulates it in that state, as shown with seq1 in the example above, is compliant, but care should always be taken to avoid memory leaks under these circumstances.

As with other out and return values, out and return sequences must not be assigned to by the caller without first copying them. This is more fully explained in Section 16.18, "Argument Passing Considerations," on page 16-41.

When a sequence is constructed with release=TRUE, a compliant application should make no assumptions about the continued lifetime of the data buffer passed to the constructor, since a compliant sequence implementation is free to copy the buffer and immediately free the original pointer.

16.11.3 Additional Memory Management Functions

ORB implementations concerned with single-process interoperability with the C mapping may overload operator new() and operator delete() for sequences so that dynamic allocation uses the same mechanism as the C language dynamic allocation functions. Whether these operators are overloaded by the implementation or not, compliant programs use new to dynamically allocate sequences and delete to free them.

Sequences also provide additional memory management functions for their buffers. For a sequence of type T, the following static member functions are provided in the sequence class public interface:

// C++
static T *allocbuf(ULong nelems);
static void freebuf(T *);

The allocbuf function allocates a vector of T elements that can be passed to the T *data constructor. The length of the vector is given by the nelems function argument. The allocbuf function initializes each element using its default constructor, except for strings, which are initialized to null pointers, and object references, which are initialized to suitably-typed nil object references. A null pointer is returned if allocbuf for some reason cannot allocate the requested vector. Vectors allocated by allocbuf should be freed using the freebuf function. The freebuf function ensures that the destructor for each element is called before the buffer is destroyed, except for string elements, which are freed using string_free(), and object reference elements, which are freed using release(). The freebuf function will ignore null pointers passed to it. Neither allocbuf nor freebuf may throw CORBA exceptions.

16.11.4 Sequence T_var Type

In addition to the regular operations defined for T_var types, the T_var for a sequence type also supports an overloaded operator[] that forwards requests to the operator[] of the underlying sequence.9 This subscript operator should have the same return type as that of the corresponding operator on the underlying sequence type.

16.12 Mapping For Array Types

Arrays are mapped to the corresponding C++ array definition, which allows the definition of statically-initialized data using the array. If the array element is a string or an object reference, then the mapping uses the same type as for structure members. That is, assignment to an array element will release the storage associated with the old value.

// IDL
typedef float F[10];
typedef string V[10];
typedef string M[1][2][3];
void op(out F p1, out V p2, out M p3);
// C++
F f1; F_var f2;
V v1; V_var v2;
M m1; M_var m2;

f(f2, v2, m2);
f1[0] = f2[1];
v1[1] = v2[1]; // free old storage, copy
m1[0][1][2] = m2[0][1][2]; // free old storage, copy

In the above example, the last two assignments result in the storage associated with the old value of the left-hand side being automatically released before the value from the right-hand side is copied.

As shown in Table 24 on page 16-44, out and return arrays are handled via pointer to array slice, where a slice is an array with all the dimensions of the original specified except the first one. As a convenience for application declaration of slice types, the mapping also provides a typedef for each array slice type. The name of the slice typedef consists of the name of the array type followed by the suffix "_slice". For example:

// IDL
typedef long LongArray[4][5];
// C++
typedef Long LongArray[4][5];
typedef Long LongArray_slice[5];

A T_var type for an array should overload operator[] instead of operator->. The use of array slices also means that a T_var type for an array should have a constructor and assignment operator that each take a pointer to array slice as a parameter, rather than T*. The T_var for the previous example would be:

// C++
class LongArray_var
{
public:
LongArray_var();
LongArray_var(LongArray_slice*);
LongArray_var(const LongArray_var &);
~LongArray_var();
LongArray_var &operator=(LongArray_slice*);
LongArray_var &operator=(const LongArray_var &);

LongArray_slice &operator[](ULong index);
const LongArray_slice &operator[](Ulong index) const;
// other conversion operators to support
// parameter passing
};

Because arrays are mapped into regular C++ arrays, they present special problems for the type-safe any mapping described in Section 16.14. To facilitate their use with the any mapping, a compliant implementation must also provide for each array type a distinct C++ type whose name consists of the array name followed by the suffix _forany. These types must be distinct so as to allow functions to be overloaded on them. Like Array_var types, Array_forany types allow access to the underlying array type, but unlike Array_var, the Array_forany type does not delete the storage of the underlying array upon its own destruction. This is because the Any mapping retains storage ownership, as described in Section 16.14.3.

The interface of the Array_forany type is identical to that of the Array_var type, but it may not be implemented as a typedef to the Array_var type by a compliant implementation since it must be distinguishable from other types for purposes of function overloading. Also, the Array_forany constructor taking an Array_slice* parameter also takes a Boolean nocopy parameter which defaults to FALSE:

// C++
class Array_forany
{
public:
Array_forany(Array_slice*, Boolean nocopy = FALSE);
...
};

The nocopy flag allows for a non-copying insertion of an Array_slice* into an Any.

Each Array_forany type must be defined at the same level of nesting as its Array type.

For dynamic allocation of arrays, compliant programs must use special functions defined at the same scope as the array type. For array T, the following functions will be available to a compliant program:

// C++
T_slice *T_alloc();
T_slice *T_dup(const T_slice*);
void T_free(T_slice *);

The T_alloc function dynamically allocates an array, or returns a null pointer if it cannot perform the allocation. The T_dup function dynamically allocates a new array with the same size as its array argument, copies each element of the argument array into the new array, and returns a pointer to the new array. If allocation fails, a null pointer is returned. The T_free function deallocates an array that was allocated with T_alloc or T_dup. Passing a null pointer to T_free is acceptable and results in no action being performed. These functions allow ORB implementations to utilize special memory management mechanisms for array types if necessary, without forcing them to replace global operator new and operator new[].

The T_alloc, T_dup, and T_free functions may not throw CORBA exceptions.

16.13 Mapping For Typedefs

A typedef creates an alias for a type. If the original type maps to several types in C++, then the typedef creates the corresponding alias for each type. The example below illustrates the mapping.

// IDL
typedef long T;
interface A1;
typedef A1 A2;
typedef sequence<long> S1;
typedef S1 S2;
// C++
typedef Long T;

// ...definitions for A1...

typedef A1 A2;
typedef A1_ptr A2_ptr;
typedef A1Ref A2Ref;
typedef A1_var A2_var;

// ...definitions for S1...

typedef S1 S2;
typedef S1_var S2_var;

For a typedef of an IDL type that maps to multiple C++ types, such as arrays, the typedef maps to all of the same C++ types and functions that its base type requires. For example:

// IDL
typedef long array[10];
typedef array another_array;
// C++
// ...C++ code for array not shown...
typedef array another_array;
typedef array_var another_array_var;
typedef array_slice another_array_slice;
typedef array_forany another_array_forany;

inline another_array_slice *another_array_alloc() {
return array_alloc();
}

inline another_array_slice* another_array_dup(another_array_slice *a) {
return array_dup(a);
}

inline void another_array_free(another_array_slice *a) {
array_free(a);
}

16.14 Mapping for the Any Type

A C++ mapping for the OMG IDL type any must fulfill two different requirements:

16.14.1 Handling Typed Values

To decrease the chances of creating an any with a mismatched TypeCode and value, the C++ function overloading facility is utilized. Specifically, for each distinct type in an OMG IDL specification, overloaded functions to insert and extract values of that type are provided by each ORB implementation. Overloaded operators are used for these functions so as to completely avoid any name space pollution. The nature of these functions, which are described in detail below, is that the appropriate TypeCode is implied by the C++ type of the value being inserted into or extracted from the any.

Since the type-safe any interface described below is based upon C++ function overloading, it requires C++ types generated from OMG IDL specifications to be distinct. However, there are special cases in which this requirement is not met:

16.14.2 Insertion into Any

To allow a value to be set in an any in a type-safe fashion, an ORB implementation must provide the following overloaded operator function for each separate OMG IDL type T:

// C++
void operator<<=(Any&, T);

This function signature suffices for types that are normally passed by value:

16.14.3 Extraction From Any

To allow type-safe retrieval of a value from an any, the mapping provides the following operators for each OMG IDL type T:

// C++
Boolean operator>>=(const Any&, T&);

This function signature suffices for primitive types that are normally passed by value. For values of type T that are too large to be passed by value efficiently, this function may be prototyped as follows:

// C++
Boolean operator>>=(const Any&, T*&);

The first form of this function is used only for the following types:

16.14.4 Distinguishing boolean, octet, char, and bounded string

Since the boolean, octet, and char OMG IDL types are not required to map to distinct C++ types, another means of distinguishing them from each other is necessary so that they can be used with the type-safe Any interface. Similarly, since both bounded and unbounded strings map to char*, another means of distinguishing them must be provided. This is done by introducing several new helper types nested in the Any class interface. For example, this can be accomplished as shown below:

// C++
class Any
{
public:
// special helper types needed for boolean, octet, char,
// and bounded string insertion
struct from_boolean {
from_boolean(Boolean b) : val(b) {}
Boolean val;
};
struct from_octet {
from_octet(Octet o) : val(o) {}
Octet val;
};
struct from_char {
from_char(Char c) : val(c) {}
Char val;
};
struct from_string {
from_string(char* s, ULong b,
Boolean nocopy = FALSE) :
val(s), bound(b) {}
char *val;
ULong bound;
};

void operator<<=(from_boolean);
void operator<<=(from_char);
void operator<<=(from_octet);
void operator<<=(from_string);

// special helper types needed for boolean, octet,
// char, and bounded string extraction
struct to_boolean {
to_boolean(Boolean &b) : ref(b) {}
Boolean &ref;
};
struct to_char {
to_char(Char &c) : ref(c) {}
Char &ref;
};
struct to_octet {
to_octet(Octet &o) : ref(o) {}
Octet &ref;
};
struct to_string {
to_string(char *&s, ULong b) : val(s), bound(b) {}
char *&val;
ULong bound;
};

Boolean operator>>=(to_boolean) const;
Boolean operator>>=(to_char) const;
Boolean operator>>=(to_octet) const;
Boolean operator>>=(to_string) const;

// other public Any details omitted

private:
// these functions are private and not implemented
// hiding these causes compile-time errors for
// unsigned char
void operator<<=(unsigned char);
Boolean operator>>=(unsigned char &) const;
};

An ORB implementation provides the overloaded operator<<= and operator>>= functions for these special helper types. These helper types are used as shown here:

// C++
Boolean b = TRUE;
Any any;
any <<= Any::from_boolean(b);
// ...
if (any >>= Any::to_boolean(b)) {
// ...any contained a Boolean...
}

char* p = "bounded";
any <<= Any::from_string(p, 8);
// ...
if (any >>= Any::to_string(p, 8)) {
// ...any contained a string<8>...
}

A bound value of 0 (zero) indicates an unbounded string.

For non-copying insertion of a bounded or unbounded string into an Any, the nocopy flag on the from_string constructor should be set to TRUE:

// C++
char* p = string_alloc(8);
// ...initialize string p...
any <<= Any::from_string(p, 8, 1); // any consumes p

Assuming that boolean, char, and octet all map the C++ type unsigned char, the private and unimplemented operator<<= and operator>>= functions for unsigned char will cause a compile-time error if straight insertion or extraction of any of the Boolean, Char, or Octet types is attempted:

// C++
Octet oct = 040;
Any any;
any <<= oct; // this line will not compile
any <<= Any::from_octet(oct); // but this one will

It is important to note that the example shown above is only one possible implementation for these helpers, not a mandated one. Other compliant implementations are possible, such as providing them via inlined static Any member functions if boolean, char, and octet are in fact mapped to distinct C++ types. All compliant C++ mapping implementations must provide these helpers, however, for purposes of portability.

16.14.5 Widening to Object

Sometimes it is desirable to extract an object reference from an Any as the base Object type. This can be accomplished using a helper type similar to those required for extracting Boolean, Char, and Octet:

// C++
class Any
{
public:
...
struct to_object {
to_object(Object_ptr &obj) : ref(obj) {}
Object_ptr &ref;
};
Boolean operator>>=(to_object) const;
...
};

The to_object helper type is used to extract an object reference from an Any as the base Object type. If the Any contains a value of an object reference type as indicated by its TypeCode, the extraction function operator>>=(to_object) explicitly widens its contained object reference to Object and returns true, otherwise it returns false. This is the only object reference extraction function that performs widening on the extracted object reference. As with regular object reference extraction, no duplication of the object reference is performed by the to_object extraction operator.

16.14.6 Handling Untyped Values

Under some circumstances the type-safe interface to Any is not sufficient. An example is a situation in which data types are read from a file in binary form and used to create values of type Any. For these cases, the Any class provides a constructor with an explicit TypeCode and generic pointer:

// C++
Any(TypeCode_ptr tc, void *value, Boolean release = FALSE);

The constructor is responsible for duplicating the given TypeCode pseudo object reference. If the release parameter is TRUE, then the Any object assumes ownership of the storage pointed to by the value parameter. A compliant application should make no assumptions about the continued lifetime of the value parameter once it has been handed to an Any with release=TRUE, since a compliant Any implementation is allowed to copy the value parameter and immediately free the original pointer. If the release parameter is FALSE (the default case), then the Any object assumes the caller will manage the memory pointed to by value. The value parameter can be a null pointer.

The Any class also defines three unsafe operations:

// C++
void replace(
TypeCode_ptr,
void *value,
Boolean release = FALSE
);
TypeCode_ptr type() const;
const void *value() const;

The replace function is intended to be used with types that cannot be used with the type-safe insertion interface, and so is similar to the constructor described above. The existing TypeCode is released and value storage deallocated, if necessary. The TypeCode function parameter is duplicated. If the release parameter is TRUE, then the Any object assumes ownership for the storage pointed to by the value parameter. A compliant application should make no assumptions about the continued lifetime of the value parameter once it has been handed to the Any::replace function with release=TRUE, since a compliant Any implementation is allowed to copy the value parameter and immediately free the original pointer. If the release parameter is FALSE (the default case), then the Any object assumes the caller will manage the memory occupied by the value. The value parameter of the replace function can be a null pointer.

For C++ mapping implementations that use Environment parameters to pass exception information, the default release argument can be simulated by providing two overloaded replace functions, one that takes a non-defaulted release parameter and one that takes no release parameter. The second function simply invokes the first with the release parameter set to FALSE.

Note that neither the constructor shown above nor the replace function is type-safe. In particular, no guarantees are made by the compiler or runtime as to the consistency between the TypeCode and the actual type of the void* argument. The behavior of an ORB implementation when presented with an Any that is constructed with a mismatched TypeCode and value is not defined.

The type function returns a TypeCode_ptr pseudo-object reference to the TypeCode associated with the Any. Like all object reference return values, the caller must release the reference when it is no longer needed, or assign it to a TypeCode_var variable for automatic management.

The value function returns a pointer to the data stored in the Any. If the Any has no associated value, the value function returns a null pointer. The type to which the void* returned by the value function may be cast depends on the ORB implementation; thus, use of the value function is not portable across ORB implementations and its usage is therefore deprecated. Note that ORB implementations are allowed to make stronger guarantees about the void* returned from the value function, if so desired.

16.14.7 Any Constructors, Destructor, Assignment Operator

The default constructor creates an Any with a TypeCode of type tk_null, and no value. The copy constructor calls _duplicate on the TypeCode_ptr of its Any parameter and deep-copies the parameter's value. The assignment operator releases its own TypeCode_ptr and deallocates storage for the current value if necessary, then duplicates the TypeCode_ptr of its Any parameter and deep-copies the parameter's value. The destructor calls release on the TypeCode_ptr and deallocates storage for the value, if necessary.

Other constructors are described in Section 16.14.6, "Handling Untyped Values," on page 16-36.

ORB implementations concerned with single-process interoperability with the C mapping may overload operator new() and operator delete() for Anys so that dynamic allocation uses the same mechanism as the C language dynamic allocation functions. Whether these operators are overloaded by the implementation or not, compliant programs use new to dynamically allocate anys and delete to free them.

16.14.8 The Any Class

The full definition of the Any class can be found in "Any Class" on page C-2.

16.14.9 The Any_var Class

Since Anys are returned via pointer as out and return parameters (see Table 24 on page 16-44), there exists an Any_var class similar to the T_var classes for object references. Any_var obeys the rules for T_var classes described in Section 16.8, calling delete on its Any* when it goes out of scope or is otherwise destroyed. The full interface of the Any_var class is shown in Section C.4, "Any_var Class," on page C-4.

16.15 Mapping for Exception Types

An OMG IDL exception is mapped to a C++ class that derives from the standard UserException class defined in the CORBA module (see Section 16.1.3, "CORBA Module," on page 16-2). The generated class is like a variable-length struct, regardless of whether or not the exception holds any variable-length members. Just as for variable-length structs, each exception member must be self-managing with respect to its storage.

The copy constructor, assignment operator, and destructor automatically copy or free the storage associated with the exception. For convenience, the mapping also defines a constructor with one parameter for each exception member-this constructor initializes the exception members to the given values. For exception types that have a string member, this constructor should take a const char* parameter, since the constructor must copy the string argument. Similarly, constructors for exception types that have an object reference member must call _duplicate on the corresponding object reference constructor parameter. The default constructor performs no explicit member initialization.

The UserException class is derived from a base Exception class, which is also defined in the CORBA module.

All standard exceptions are derived from a SystemException class, also defined in the CORBA module. Like UserException, SystemException is derived from the base Exception class. The SystemException class interface is shown below.

// C++
enum CompletionStatus {
COMPLETED_YES,
COMPLETED_NO,
COMPLETED_MAYBE
};
class SystemException : public Exception
{
public:
SystemException();
SystemException(const SystemException &);
SystemException(ULong minor, CompletionStatus status);
~SystemException();
SystemException &operator=(const SystemException &);

ULong minor() const;
void minor(ULong);

CompletionStatus completed() const;
void completed(CompletionStatus);
};

The default constructor for SystemException causes minor() to return 0 and completed() to return COMPLETED_NO.

Each specific system exception (described in Section 14.1.6, "Exceptions," on page 14-3) is derived from SystemException:

// C++
class UNKNOWN : public SystemException { ... };
class BAD_PARAM : public SystemException { ... };
// etc.

All specific system exceptions are defined within the CORBA module.

This exception hierarchy allows any exception to be caught by simply catching the Exception type:

// C++
try {
...
} catch (const Exception &exc) {
...
}

Alternatively, all user exceptions can be caught by catching the UserException type, and all system exceptions can be caught by catching the SystemException type:

// C++
try {
...
} catch (const UserException &ue) {
...
} catch (const SystemException &se) {
...
}

Naturally, more specific types can also appear in catch clauses.

Exceptions are normally thrown by value and caught by reference. This approach lets the exception destructor release storage automatically.

C++ compilers that support official C++ Run Time Type Information (RTTI) need not support narrowing for the Exception hierarchy. RTTI supports, among other things, determination of the run-time type of a C++ object. In particular, the dynamic_cast<T*> operator11 allows for narrowing from a base pointer to a more derived pointer if the object pointed to really is of the more derived type. This operator is not useful for narrowing object references, since it cannot determine the actual type of remote objects, but it can be used to narrow within the exception hierarchy. Since catch clauses can catch by type, this feature is mainly of use for narrowing exceptions received via Environments from the DII.

For those C++ environments that do not support dynamic_cast<T*>, the exception hierarchy provides a narrowing mechanism. This is described in"Without Run-Time Type Information (RTTI)" on page D-3.

Request invocations made through the DII may result in user-defined exceptions that cannot be fully represented in the calling program because the specific exception type was not known at compile-time. The mapping provides the UnknownUserException so that such exceptions can be represented in the calling process:

// C++
class UnknownUserException : public UserException
{
public:
Any &exception();
};

As shown here, UnknownUserException is derived from UserException. It provides the exception() accessor that returns an Any holding the actual exception. Ownership of the returned Any is maintained by the UnknownUserException-the Any merely allows access to the exception data. Conforming applications should never explicitly throw exceptions of type UnknownUserException-it is intended for use with the DII.

16.16 Mapping For Operations and Attributes

An operation maps to a C++ function with the same name as the operation. Each read-write attribute maps to a pair of overloaded C++ functions (both with the same name), one to set the attribute's value and one to get the attribute's value. The set function takes an in parameter with the same type as the attribute, while the get function takes no parameters and returns the same type as the attribute. An attribute marked readonly maps to only one C++ function, to get the attribute's value. Parameters and return types for attribute functions obey the same parameter passing rules as for regular operations.

OMG IDL oneway operations are mapped the same as other operations; that is, there is no way to know by looking at the C++ whether an operation is oneway or not.

The mapping does not define whether exceptions specified for an OMG IDL operation are part of the generated operation's type signature or not.

// IDL
interface A
{
void f();
oneway void g();
attribute long x;
};
// C++
A_var a;
a->f();
a->g();
Long n = a->x();
a->x(n + 1);

Unlike the C mapping, C++ operations do not require an additional Environment parameter for passing exception information-real C++ exceptions are used for this purpose. See Section 16.15, "Mapping for Exception Types," on page 16-38 and "Without Exception Handling" on page D-1 for more details.

16.17 Implicit Arguments to Operations

If an operation in an OMG IDL specification has a context specification, then a Context_ptr input parameter (see Section 17.8.1, "Context Interface," on page 17-8) follows all operation-specific arguments. In an implementation that does not support real C++ exceptions, an output Environment parameter is the last argument, following all operation-specific arguments, and following the context argument if present. The parameter passing mode for Environment is described in "Without Exception Handling" on page D-1.

16.18 Argument Passing Considerations

The mapping of parameter passing modes attempts to balance the need for both efficiency and simplicity. For primitive types, enumerations, and object references, the modes are straightforward, passing the type P for primitives and enumerations and the type A_ptr for an interface type A.

Aggregate types are complicated by the question of when and how parameter memory is allocated and deallocated. Mapping in parameters is straightforward because the parameter storage is caller-allocated and read-only. The mapping for out and inout parameters is more problematic. For variable-length types, the callee must allocate some if not all of the storage. For fixed-length types, such as a Point type represented as a struct containing three floating point members, caller allocation is preferable (to allow stack allocation).

To accommodate both kinds of allocation, avoid the potential confusion of split allocation, and eliminate confusion with respect to when copying occurs, the mapping is T& for a fixed-length aggregate T and T*& for a variable-length T. This approach has the unfortunate consequence that usage for structs depends on whether the struct is fixed- or variable-length; however, the mapping is consistently T_var& if the caller uses the managed type T_var.

The mapping for out and inout parameters additionally requires support for deallocating any previous variable-length data in the parameter when a T_var is passed. Even though their initial values are not sent to the operation, we include out parameters because the parameter could contain the result from a previous call. There are many ways to implement this support. The mapping does not require a specific implementation, but a compliant implementation must free the inaccessible storage associated with a parameter passed as a T_var managed type. The following examples demonstrate the compliant behavior:

// IDL
struct S { string name; float age; };
void f(out S p);
// C++
S_var s;
f(s);
// use s
f(s); // first result will be freed

S *sp; // need not initialize before passing to out
f(sp);
// use sp
delete sp; // cannot assume next call will free old value
f(sp);

Note that implicit deallocation of previous values for out and inout parameters works only with T_var types, not with other types:

// IDL
void q(out string s);
// C++
char *s;
for (int i = 0; i < 10; i++)
q(s); // memory leak!

Each call to the q function in the loop results in a memory leak because the caller is not invoking string_free on the out result. There are two ways to fix this, as shown below:

// C++
char *s;
String_var svar;
for (int i = 0 ; i < 10; i++) {
q(s);
string_free(s); // explicit deallocation
// OR:
q(svar); // implicit deallocation
}

Using a plain char* for the out parameter means that the caller must explicitly deallocate its memory before each reuse of the variable as an out parameter, while using a String_var means that any deallocation is performed implicitly upon each use of the variable as an out parameter.

Variable-length data must be explicitly released before being overwritten. For example, before assigning to an inout string parameter, the implementor of an operation may first delete the old character data. Similarly, an inout interface parameter should be released before being reassigned. One way to ensure that the parameter storage is released is to assign it to a local T_var variable with an automatic release, as in the following example:

// IDL
interface A;
void f(inout string s, inout A obj);
// C++
void Aimpl::f(char *&s, A_ptr &obj) {
String_var s_tmp = s;
s = /* new data */;
A_var obj_tmp = obj;
obj = /* new reference */
}

To allow the callee the freedom to allocate a single contiguous area of storage for all the data associated with a parameter, we adopt the policy that the callee-allocated storage is not modifiable by the caller. However, trying to enforce this policy by returning a const type in C++ is problematic, since the caller is required to release the storage, and calling delete on a const object is an error12. A compliant mapping therefore is not required to detect this error.

For parameters that are passed or returned as a pointer (T*) or reference to pointer (T*&), a compliant program is not allowed to pass or return a null pointer; the result of doing so is undefined. In particular, a caller may not pass a null pointer under any of the following circumstances:



[Top] [Prev] [Next] [Bottom]

1 When T_ptr is mapped to T*, it is impossible in C++ to provide implicit widening between T_var types while also providing the necessary duplication semantics for T_ptr types.

2 This can be achieved by deriving all T_var types for object references from a base _var class, then making the assignment operator for _var private within each T_var type.

3 A recent change made to the C++ language by the ANSI/ISO C++ standardization committees allows static integer constants to be initialized within the class declaration, so for some C++ compilers, the code generation issues described here may not be a problem.

4 Examples and descriptions in this document still use TRUE and FALSE for purposes of clarity.

5 Transmissible pseudo-objects are listed as "general arguments" in Section TBL. 14, "Pseudo-objects," on page A-2.

6 Those implementations concerned with data layout compatibility with the C mapping in this manual will also want to ensure that the sizes of these members match those of their C mapping counterparts.

7 A separate modifier for String_var is needed because it can automatically convert to both a char* and a const char*; since unions provide modifiers for both of these types, an attempt to set a string member of a union from a String_var would otherwise result in an ambiguity error at compile time.

8 A return type of char* allowing read-write access could mistakenly be assigned to a String_var, resulting in the String_var and the union both assuming ownership for the string's storage.

9 Note that since T_var types do not handle const T*, there is no need to provide the const version of operator[] for Sequence_var types.

10 A mapping implementor may use the new C++ keyword "explicit" to prevent implicit conversions through the Array_forany constructor, but this feature is not yet widely available in current C++ compilers.

11 It is unlikely that a compiler would support RTTI without supporting exceptions, since much of a C++ exception handling implementation is based on RTTI mechanisms.

12 It is very likely that the upcoming ANSI/ISO C++ standard will allow delete on a const object, but many C++ compilers do not yet support this feature.

13 When real C++ exceptions are not available, however, it is important that null pointers are returned whenever an Environment containing an exception is returned; see "Without Exception Handling" on page D-1 for more details.

pubs@omg.org
Copyright © 1995, Object Management Group. All rights reserved.