Hi Andrew,
you can find a detailed discussion of Ptr types in the client-side C++ mapping chapter.
Structs are value types, as you would expect. Classes are value types, but operations on classes can be accessed via a proxy, that is, classes can act as servants. Here is a simple class:
Code:
class X { int i; };
That class behaves just like a struct, that is, it is passed by value. A more complex example:
Code:
class Base { int i; };
class Derived extends X { int j; };
That's one difference between classes and structs: classes support inheritance.
If you pass an instance of this class, the most-derived type of the class that is understood by the receiver is what the receiver gets. For example:
Code:
interface Example {
void foo(Base b);
};
At run time, you can pass a Base instance to foo(). If you do, the receiver (obviously) receives that Base instance. On the other hand, at run time, you can pass a Derived instance to foo(). If you do, the receiver will receive the derived instance provided that the receiver has knowledge of the type Derived. On the other hand, if the receiver doesn't have knowledge of the type Derived (for example, because type Derived was added to the type system after the receiver was compiled), the Derived instance is sliced to type Base, and the receiver gets a Base.
For classes that point at other classes (that is, classes with class members), you can create graphs. If you pass one node of such a graph to an operation, all nodes that are reachable from that starting node are marshaled to the receiver. In other words, the entire graph of class instances is rebuilt in the receiver.
So, you can think of classes as structs on steroids: they support inheritance, slicing to a base type if the receiver doesn't have knowledge of the actual most-derived type, and they support pointer semantics. (Structures don't do any of these things.)
For classes with operations, their value semantics do not change. Classes are still passed by value, that is, their data members are marshaled. Invoking an operation on a class via its Ptr always invokes the operation on the copy of the class in the local address space. Ergo, the code that implements the operations on a class must be linked into the local address space for that to work. (This is also the reason why you have to register a class factory for classes with operations--without the factory, the Ice run time would have no idea which programming-language class to instantiate because that class must implement the operations.)
You can also use a class as the implementation of an interface:
Code:
interface Base {
void foo();
};
class Derived implements Base {
{
int i;
void bar();
};
Now if you pass an instance of this class around, it will still be passed by value, that is, the value of the data member i will be sent to the other end, and calling bar() via a class's Ptr calls the bar() in the local address space. Similarly, you can invoke the base operation foo() on the class via its Ptr. If you do, you will also call the foo() in the local address space.
However, if a class implements an interface, you can also create a proxy to that class. In that case, you have created a proxy to an instance of the class, but the class can live in a remote address space. In other words, if you create a proxy to a class that implements an interface, you have effectively created a proxy to an Ice object that implements the interface supported by the class.
For the above example, if you create a BasePrx, you can call foo() via that proxy. The invocation will go to whatever address space is denoted by the proxy. Similarly, you can create a DerviedPrx. Via the DerivedPrx, you can call both foo() and bar(), and either invocation will go to the address space denoted by the proxy.
So, in summary, invocations via a Ptr are always local, and invocations via Prx go to the address space denoted by the proxy (which may be local but, typically, is remote).
Finally, you can declare an operation like this:
Code:
interface Base { /* ... */ };
interface Example {
void op(Base b);
};
Note that op() accepts an interface by value.
Of course, interfaces cannot be passed by value. However, classes that implement the interfaces can. In other words, op() is an operation that accepts a class that implements Base and sends that class instance by value to the other end. Of course, the receiver will get a BasePtr from the Ice run time and, to access the actual derived part, will have to do a type-safe down-cast on the Ptr, for example, by calling DerivedPtr::dynamicCast(b).
Hope this helps!
Cheers,
Michi.