C++98 Mapping for Interfaces

The mapping of Slice interfaces revolves around the idea that, to invoke a remote operation, you call a member function on a local class instance that is a proxy for the remote object. This makes the mapping easy and intuitive to use because making a remote procedure call is no different from making a local procedure call (apart from error semantics).

On this page:

Proxy Classes and Proxy Handles

On the client side, a Slice interface maps to a class with member functions that correspond to the operations on that interface. Consider the following simple interface:

Slice
module M
{
    interface Simple
    {
        void op();
    }
}

The Slice compiler generates the following definitions for use by the client:

C++
namespace IceProxy
{
    namespace M
    {
        class Simple;
    }
}

namespace M 
{
    class Simple;
    typedef IceInternal::ProxyHandle< ::IceProxy::M::Simple> SimplePrx;
    typedef IceInternal::Handle< ::M::Simple> SimplePtr;
}

namespace IceProxy
{
    namespace M
    {
        class Simple : public virtual IceProxy::Ice::Object
        {
        public:
            void op();
            void op(const Ice::Context&);
            // ...
        };
    };
}

As you can see, the compiler generates a proxy class Simple in the IceProxy::M namespace, as well as a proxy handle M::SimplePrx. In general, for a module M, the generated names are ::IceProxy::M::<interface-name> and ::M::<interface-name>Prx.

In the client's address space, an instance of IceProxy::M::Simple is the local ambassador for a remote instance of the Simple interface in a server and is known as a proxy class instance. All the details about the server-side object, such as its address, what protocol to use, and its object identity are encapsulated in that instance.

Inheritance from Ice::Object

Simple inherits from IceProxy::Ice::Object, reflecting the fact that all Ice interfaces implicitly inherit from Ice::Object. For each operation in the interface, the proxy class has two overloaded member functions of the same name. For the preceding example, we find that the operation op has been mapped to two member functions op.

One of the overloaded member functions has a trailing parameter of type Ice::Context. This parameter is for use by the Ice run time to store information about how to deliver a request; normally, you do not need to supply a value here and can pretend that the trailing parameter does not exist. (The parameter is also used by IceStorm.)

Interface Inheritance

Inheritance relationships among Slice interfaces are maintained in the generated C++ classes. For example:

Slice
module M
{
    interface A { ... }
    interface B { ... }
    interface C extends A, B { ... }
}

The generated code for C reflects the inheritance hierarchy:

C++
namespace IceProxy
{
    namespace M
    {
        class C : public virtual A, public virtual B
        {
            ...
        };
    }
}

Given a proxy for C, a client can invoke any operation defined for interface C, as well as any operation inherited from C's base interfaces.

Proxy Handles

Client-side application code never manipulates proxy class instances directly. In fact, you are not allowed to instantiate a proxy class directly. The following code will not compile because the proxy class does not provide a public default constructor:

C++
IceProxy::M::Simple s;  // Compile-time error!

Proxy instances are always instantiated on behalf of the client by the Ice run time, so client code never has any need to instantiate a proxy directly. When the client receives a proxy from the run time, it is given a proxy handle to the proxy, of type <interface-name>Prx (SimplePrx for the preceding example). The client accesses the proxy via its proxy handle; the handle takes care of forwarding operation invocations to its underlying proxy, as well as reference-counting the proxy. This means that no memory-management issues can arise: deallocation of a proxy is automatic and happens once the last handle to the proxy disappears (goes out of scope).

Because the application code always uses proxy handles and never touches the proxy class directly, we usually use the term proxy to denote both proxy handle and proxy class. This reflects the fact that, in actual use, the proxy handle looks and feels like the underlying proxy class instance. If the distinction is important, we use the terms proxy class, proxy class instance, and proxy handle.

Methods on Proxy Handles

As we saw for the preceding example, the handle is actually a template of type IceInternal::ProxyHandle that takes the proxy class as the template parameter. This template has the usual default constructor, copy constructor, and assignment operator.

Default constructor

You can default-construct a proxy handle. The default constructor creates a proxy that points nowhere (that is, points at no object at all). If you invoke an operation on such a null proxy, you get an IceUtil::NullHandleException:

C++
try
{
    SimplePrx s;        // Default-constructed proxy
    s->op();            // Call via nil proxy
    assert(0);          // Can't get here
}
catch(const IceUtil::NullHandleException&)
{
    cout << "As expected, got a NullHandleException" << endl;
}

Copy constructor

The copy constructor ensures that you can construct a proxy handle from another proxy handle. Internally, this increments a reference count on the proxy; the destructor decrements the reference count again and, once the count drops to zero, deallocates the underlying proxy class instance. That way, memory leaks are avoided:

C++
{                               // Enter new scope
    SimplePrx s1 = ...;         // Get a proxy from somewhere
    SimplePrx s2(s1);           // Copy-construct s2
    assert(s1 == s2);           // Assertion passes
}                               // Leave scope; s1, s2, and the
                                // underlying proxy instance
                                // are deallocated

Note the assertion in this example: proxy handles support comparison.

Assignment operator

You can freely assign proxy handles to each other. The handle implementation ensures that the appropriate memory-management activities take place. Self-assignment is safe and you do not have to guard against it:

C++
SimplePrx s1 = ...;     // Get a proxy from somewhere
SimplePrx s2;           // s2 is nil
s2 = s1;                // both point at the same object
s1 = 0;                 // s1 is nil
s2 = 0;                 // s2 is nil

Widening assignments work implicitly. For example, if we have two interfaces, Base and Derived, we can widen a DerivedPrx to a BasePrx implicitly:

C++
BasePrx base;
DerivedPrx derived;
base = derived;         // Fine, no problem
derived = base;         // Compile-time error

Implicit narrowing conversions result in a compile error, so the usual C++ semantics are preserved: you can always assign a derived type to a base type, but not vice versa.

Checked cast

Proxy handles provide a checkedCast method:

C++
namespace IceInternal
{
    template<typename T>
    class ProxyHandle : public IceUtil::HandleBase<T>
    {
    public:
        template<class Y>
        static ProxyHandle checkedCast(const ProxyHandle<Y>& r);

        template<class Y>
        static ProxyHandle checkedCast(const ProxyHandle<Y>& r, const ::Ice::Context& c);

        // ...
    };
}

A checked cast has the same function for proxies as a C++ dynamic_cast has for pointers: it allows you to assign a base proxy to a derived proxy. If the type of the base proxy's target object is compatible with the derived proxy's static type, the assignment succeeds and, after the assignment, the derived proxy denotes the same object as the base proxy. Otherwise, if the type of the base proxy's target object is incompatible with the derived proxy's static type, the derived proxy is set to null. Here is an example to illustrate this:

C++
BasePrx base = ...;     // Initialize base proxy
DerivedPrx derived = DerivedPrx::checkedCast(base);
if(derived)
{
    // Base has run-time type Derived,
    // use derived...
} 
else 
{
    // Base has some other, unrelated type
}

The expression DerivedPrx::checkedCast(base) tests whether base points at an object of type Derived (or an object with a type that is derived from Derived). If so, the cast succeeds and derived is set to point at the same object as base. Otherwise, the cast fails and derived is set to the null proxy.

Note that checkedCast is a static member function so, to do a down-cast, you always use the syntax <interface-name>Prx::checkedCast.

Also note that you can use proxies in boolean contexts. For example, if (proxy) returns true if the proxy is not null.

A checkedCast typically results in a remote message to the server.The message effectively asks the server "is the object denoted by this reference of type Derived?"

Calling checkedCast on a proxy that is already of the desired proxy type returns immediately that proxy. Otherwise, checkedCast always calls ice_isA on the target object, and upon success, creates a new instance of the desired proxy class.

The reply from the server is communicated to the application code in form of a successful (non-null) or unsuccessful (null) result. Sending a remote message is necessary because, as a rule, there is no way for the client to find out what the actual run-time type of the target object is without confirmation from the server. (For example, the server may replace the implementation of the object for an existing proxy with a more derived one.) This means that you have to be prepared for a checkedCast to fail. For example, if the server is not running, you will receive a ConnectFailedException; if the server is running, but the object denoted by the proxy no longer exists, you will receive an ObjectNotExistException.

Unchecked cast

In some cases, it is known that an object supports a more derived interface than the static type of its proxy. For such cases, you can use an unchecked down-cast:

C++
namespace IceInternal
{
    template<typename T>
    class ProxyHandle : public IceUtil::HandleBase<T>
    {
    public:
        template<class Y>
        static ProxyHandle uncheckedCast(const ProxyHandle<Y>& r);
        // ...
    };
}

An uncheckedCast provides a down-cast without consulting the server as to the actual run-time type of the object, for example:

C++
BasePrx base = ...;     // Initialize to point at a Derived
DerivedPrx derived;
derived = DerivedPrx::uncheckedCast(base);
// Use derived...

You should use an uncheckedCast only if you are certain that the target object indeed supports the more derived type: an uncheckedCast, as the name implies, is not checked in any way; it does not contact the object in the server and, if it fails, it does not return null. If you use the proxy resulting from an incorrect uncheckedCast to invoke an operation, the behavior is undefined. Most likely, you will receive an OperationNotExistException, but, depending on the circumstances, the Ice run time may also report an exception indicating that unmarshaling has failed, or even silently return garbage results.

Calling uncheckedCast on a proxy that is already of the desired proxy type returns immediately that proxy. Otherwise, uncheckedCast creates a new instance of the desired proxy class.

Despite its dangers, uncheckedCast is still useful because it avoids the cost of sending a message to the server. And, particularly during initialization, it is common to receive a proxy of static type Ice::Object, but with a known run-time type. In such cases, an uncheckedCast saves the overhead of sending a remote message.

Stream insertion and stringification

For convenience, proxy handles also support insertion of a proxy into a stream, for example:

C++
Ice::ObjectPrx p = ...;
cout << p << endl;

This code is equivalent to writing:

C++
Ice::ObjectPrx p = ...;
cout << p->ice_toString() << endl;

Either code prints the stringified proxy. You could also achieve the same thing by writing:

C++
Ice::ObjectPrx p = ...;
cout << communicator->proxyToString(p) << endl;

The advantage of using the ice_toString member function instead of proxyToString is that you do not need to have the communicator available at the point of call.

Static id

The ice_staticId method returns the type ID of the proxy's Slice interface:

C++
namespace IceInternal 
{
    template<typename T>
    class ProxyHandle : public IceUtil::HandleBase<T> 
    {
    public:
        static const std::string& ice_staticId();
        // ...
    };
}

Here's an example that shows how to use it:

C++
BasePrx base = ...;
if(base->ice_id() == Derived::ice_staticId())
    // target object implements Derived

Applications normally use checkedCast instead of calling ice_id directly.

Using Proxy Methods in C++

The base proxy class ObjectPrx supports a variety of methods for customizing a proxy. Since proxies are immutable, each of these "factory methods" returns a copy of the original proxy that contains the desired modification. For example, you can obtain a proxy configured with a ten second timeout as shown below:

C++
Ice::ObjectPrx proxy = communicator->stringToProxy(...);
proxy = proxy->ice_timeout(10000);

A factory method returns a new proxy object if the requested modification differs from the current proxy, otherwise it returns the current proxy. With few exceptions, factory methods return a proxy of the same type as the current proxy, therefore it is generally not necessary to repeat a down-cast after using a factory method. The example below demonstrates these semantics:

C++
Ice::ObjectPrx base = communicator->stringToProxy(...);
HelloPrx hello = HelloPrx::checkedCast(base);
hello = hello->ice_timeout(10000); // Type is preserved
hello->sayHello();

The only exceptions are the factory methods ice_facet and ice_identity. Calls to either of these methods may produce a proxy for an object of an unrelated type, therefore they return a base proxy that you must subsequently down-cast to an appropriate type.

Object Identity and Proxy Comparison in C++

Proxy handles support comparison using the following operators:

  • operator bool
    Proxies have a conversion operator to bool. The operator returns true if a proxy is not null, and false otherwise. This allows you to write:

    C++
    BasePrx base = ...;
    if(base)
            // It's a non-nil proxy
    if(!base)
            // It's a nil proxy
    
  • operator==
    operator!=
    These operators permit you to compare proxies for equality and inequality. It's also legal to compare a proxy against the literal 0 to test whether a proxy is null, but we recommend using the bool operator instead.

  • operator<
    operator<=
    operator>
    operator>=
    Proxies support comparison. This allows you to place proxies into STL containers such as maps or sorted lists.

Note that proxy comparison uses all of the information in a proxy for the comparison. This means that not only the object identity must match for a comparison to succeed, but other details inside the proxy, such as the protocol and endpoint information, must be the same. In other words, comparison with == and != tests for proxy identity, not object identity. A common mistake is to write code along the following lines:

C++
Ice::ObjectPrx p1 = ...;        // Get a proxy...
Ice::ObjectPrx p2 = ...;        // Get another proxy...

if(p1 != p2)
{
    // p1 and p2 denote different objects       // WRONG!
}
else 
{
    // p1 and p2 denote the same object         // Correct
}

Even though p1 and p2 differ, they may denote the same Ice object. This can happen because, for example, both p1 and p2 embed the same object identity, but each use a different protocol to contact the target object. Similarly, the protocols may be the same, but denote different endpoints (because a single Ice object can be contacted via several different transport endpoints). In other words, if two proxies compare equal with ==, we know that the two proxies denote the same object (because they are identical in all respects); however, if two proxies compare unequal with ==, we know absolutely nothing: the proxies may or may not denote the same object.

To compare the object identities of two proxies, you can use helper functions in the Ice namespace:

C++
namespace Ice
{
    bool proxyIdentityLess(const ObjectPrx&, const ObjectPrx&);
    bool proxyIdentityEqual(const ObjectPrx&, const ObjectPrx&);
    bool proxyIdentityAndFacetLess(const ObjectPrx&, const ObjectPrx&);
    bool proxyIdentityAndFacetEqual(const ObjectPrx&, const ObjectPrx&);
}

The proxyIdentityEqual function returns true if the object identities embedded in two proxies are the same and ignores other information in the proxies, such as facet and transport information. To include the facet name in the comparison, use proxyIdentityAndFacetEqual instead.

The proxyIdentityLess function establishes a total ordering on proxies. It is provided mainly so you can use object identity comparison with STL sorted containers. (The function uses name as the major ordering criterion, and category as the minor ordering criterion.) The proxyIdentityAndFacetLess function behaves similarly to proxyIdentityLess, except that it also compares the facet names of the proxies when their identities are equal.

proxyIdentityEqual and proxyIdentityAndFacetLess allow you to correctly compare proxies for object identity. The example below demonstrates how to use proxyIdentityEqual:

C++
Ice::ObjectPrx p1 = ...;        // Get a proxy...
Ice::ObjectPrx p2 = ...;        // Get another proxy...

if(!Ice::proxyIdentityEqual(p1, p2)
{
    // p1 and p2 denote different objects       // Correct
}
else
{
    // p1 and p2 denote the same object         // Correct
}

See Also