On this page:
Composition using Slices
Classes and exceptions are composed of slices, where each slice corresponds to a level in the type hierarchy and contains the data members defined at that level. Consider this example showing a class hierarchy:
An instance of class
C contains the following slices, listed in order from most-derived to least-derived:
Let's add the following interface to our discussion:
Now suppose a client invokes
getObject. Let's also suppose that the server actually returns an instance of class
C, which is legal given that
C derives from
B. The Ice run time in the client knows that the formal return type of the
getObject operation is
B is the least-derived type that the client can accept for this operation. At a high level, the Ice run time in the client behaves as follows:
- It discovers that the most-derived type of the returned instance is
Cand checks whether this type is known. These checks include language-specific mechanisms as well as the value factory API.
- If this type is known to the client, Ice instantiates the object, extracts the data members for each of its slices, and returns the object.
- If type
Cis not known to the client, which can occur when the client and server are using different versions of the Slice definitions, Ice discards the data members for slice
C(also known as slicing the object) and tries again with the next slice.
- If type
Bis also not known to the client then we have a problem. First, from a logical standpoint, the client must know type
Bbecause it is the statically-declared return type of the operation that the client just invoked. Second, we cannot slice this object any further because it would no longer be compatible with the formal signature of the operation; the returned object must at least be an instance of
B, so we could not return an instance of
A. In either case, the Ice run time would raise an exception.
Generally speaking, upon receipt of an instance of a class or exception, the Ice run time discards the slices of unknown types until it finds a type that it recognizes, exhausts all slices, or can no longer satisfy the formal type signature of the operation. This slicing feature allows the receiver, whose Slice definitions may be limited or outdated, to continue to function properly even when it does not recognize the most-derived type.
Ice provides two on-the-wire formats for class and exception slices: the compact format and the sliced format. Ice uses the compact format by default, which is more space-efficient on the wire but offers less flexibility on the receiving end.
An application that needs the slicing behavior we discussed in the previous section must explicitly enable the sliced format as follows:
- Set the
Ice.Default.SlicedFormatproperty to a non-zero value to force the Ice run time to use the sliced format by default.
- Annotate your Slice definitions with
formatmetadata to selectively enable the sliced format for certain operations or interfaces.
For example, suppose an application can safely use the compact format most of the time, but still needs slicing in a few situations. In this case the application can use metadata to enable the sliced format where necessary:
The semantics implied by these definitions state that the caller of
getAccount assumes it will know every type that that might be returned, but the same cannot be said for
importAccount. By enabling the sliced format here, we allow the client to "slice off" what it does not recognize, even if that means the client is left with only an instance of
Account and not an instance of some derived type.
Now let's examine the opposite case: use the sliced format by default, and the compact format only in certain cases:
Here we specify that all operations in
Ledger use the sliced format unless overridden at the operation level, which we do for
The format affects the input parameters, output parameters, and exceptions of an operation. Consider this example:
The metadata forces the client to use the compact format for the input parameter
oldAccount, and forces the server to use the compact format for the return value or the exception. For a given operation, it is not possible to use one format for the parameters and a different format for the exceptions.
If you decide to use the
Ice.Default.SlicedFormat property, be aware that this property only affects the sender of a value or exception. For example, if you enable this property in the client but not the server, then all values sent by the client use the sliced format by default, but all values and exceptions returned by the server use the compact format by default.
By offering two alternative formats, Ice gives you a great deal of flexibility in designing your applications. The compact format is ideal for applications that place a greater emphasis on efficiency, while the sliced format is helpful when clients and servers evolve independently.
A limitation of the compact format can prevent a receiver from successfully receiving a message. Suppose a client uses the following Slice definitions:
Also suppose that the server is using a newer version of these definitions:
The server's definitions introduce a new class,
GroupInfo, and an optional data member that uses this new type.
Let's examine the case where a client receives a message containing a
UserInfo instance whose
group member is set to a non-nil value. Since the
GroupInfo type is unknown to the client, it needs to be able to skip the data associated with the
GroupInfo instance in order to process the rest of the message. The sliced format includes the information the client needs to skip instances of unknown types, but the compact format unfortunately does not include this information and the client will raise an exception.
The lesson here is that adding a new class type to your application can have unexpected repercussions, especially when using the compact format. Remember that the sender can change formats without necessarily needing to update the receiver. For example, after adding the
group member to
UserInfo, the server could begin using the sliced format for all operations that return
UserInfo to ensure that any existing clients would be able to receive it successfully.
The concept of slicing involves discarding the slices of unknown types when receiving an instance of a Slice class or exception. Here is a simple example:
The server implementing the
Relay interface must know the type
Base (because it is statically referenced in the interface definition), but may not know
Derived. Suppose the implementation of
transform involves forwarding the instance to another back-end server for processing and returning the transformed instance to the caller. In effect, the
Relay server is an intermediary. If the
Relay server does not know the types
Derived, it will slice an instance to
Base and discard the data members of any more-derived types, which is clearly not the intended result because the back-end server does know those types. The only way the
Relay server could successfully forward these instances is by knowing all possible derived types, which makes the application more difficult to evolve over time because the intermediary must be updated each time a new derived type is added.
To address this limitation, Ice supports a new metadata directive that allows an instance of a Slice class or exception to be forwarded with all of its slices intact, even if the intermediary does not know one or more of the instance's derived types. The new directive,
preserve-slice, is shown below:
With this change, all instances of
Base, and types derived from
Base, can be forwarded intact. Also notice the addition of the
format:sliced metadata on the
Relay interface, which ensures that its operations use the sliced format and not the default compact format.
If a preserved value instance is sliced upon receipt, calling
ice_getSlicedData on the value will return a
SlicedData object that represents the complete state of the instance. User exceptions provides the same
ice_getSlicedData method that returns the complete state of a sliced user exception.
SlicedData class is described below:
SliceInfo should be considered read-only even when the Ice API allows changing the fields of a
SliceInfo provide all of the information the run time requires to forward an instance; applications do not normally need to use them.
In C++11 and Objective-C, if the received value refers to a graph with cycles, the application should call
clear on the
SlicedData object associated with the value to break potential cycles.
Unknown Sliced Values
Suppose we modify our
Relay example as shown below:
The only difference here is the signature of the
transform operation, which now uses the
Value type. Technically, it is not necessary for the intermediary server to know any of the class types that might be relayed via this new definition of
transform because the formal types in its signature do not impose any requirements. As long as any value types known by the intermediary are marked with
preserve-slice, and the
transform operation uses the sliced format, this intermediary is capable of relaying values of any type.
If the Ice run time in the intermediary does not know any of the types in an object's inheritance hierarchy, and the formal type is
Value, Ice uses an instance of
UnknownSlicedValue to represent the instance:
The implementation of
transform receives an instance of
UnknownSlicedValue and can use that object as its return value. If necessary, the implementation can determine the most-derived type of the instance by calling