As we saw in Self-Referential Classes, classes naturally support pass-by-value semantics: passing a class transmits the data members of the class to the receiver. Any changes made to these data members by the receiver affect only the receiver's copy of the class; the data members of the sender's class are not affected by the changes made by the receiver.
In addition to passing a class by value, you can pass a class by reference. For example:
class TimeOfDay { short hour; short minute; short second; string format(); }; interface Example { TimeOfDay* get(); // Note: returns a proxy! };
Note that the get
operation returns a proxy to a TimeOfDay
class and not a TimeOfDay
instance itself. The semantics of this are as follows:
- When the client receives a
TimeOfDay
proxy from theget
call, it holds a proxy that differs in no way from an ordinary proxy for an interface. - The client can invoke operations via the proxy, but cannot access the data members. This is because proxies do not have the concept of data members, but represent interfaces: even though the
TimeOfDay
class has data members, only its operations can be accessed via a the proxy.
The net effect is that, in the preceding example, the server holds an instance of the TimeOfDay
class. A proxy for that instance was passed to the client. The only thing the client can do with this proxy is to invoke the format
operation. The implementation of that operation is provided by the server and, when the client invokes format
, it sends an RPC message to the server just as it does when it invokes an operation on an interface. The implementation of the format
operation is entirely up to the server. (Presumably, the server will use the data members of the TimeOfDay
instance it holds to return a string containing the time to the client.)
The preceding example looks somewhat contrived for classes only. However, it makes perfect sense if classes implement interfaces: parts of your application can exchange class instances (and, therefore, state) by value, whereas other parts of the system can treat these instances as remote interfaces.
For example:
interface Time { string format(); // ... }; class TimeOfDay implements Time { short hour; short minute; short second; }; interface I1 { TimeOfDay get(); // Pass by value void put(TimeOfDay time); // Pass by value }; interface I2 { Time* get(); // Pass by reference };
In this example, clients dealing with interface I1
are aware of the TimeOfDay
class and pass it by value whereas clients dealing with interface I2
deal only with the Time
interface. However, the actual implementation of the Time
interface in the server uses TimeOfDay
instances.
Be careful when designing systems that use such mixed pass-by-value and pass-by-reference semantics. Unless you are clear about what parts of the system deal with the interface (pass by reference) aspects and the class (pass by value) aspects, you can end up with something that is more confusing than helpful.
A good example of putting this feature to use can be found in Freeze, which allows you to add classes to an existing interface to implement persistence.