Client-Side C++ Mapping for Operations
Mapping for Operations
As we saw in the Client-Side C++ Mapping for Interfaces, for each operation on an interface, the generated proxy class contains 3 member functions for this operation. To invoke an operation, you call one of these functions on the proxy. For example, let’s take the generated code from the greeter example:
module VisitorCenter
{
interface Greeter
{
string greet(string name);
}
}
The proxy class generated from the Greeter interface, after removing extra details, is as follows:
namespace VisitorCenter
{
class GreeterPrx : public Ice::Proxy<GreeterPrx, Ice::ObjectPrx>
{
public:
GreeterPrx(const Ice::CommunicatorPtr& communicator,
std::string_view proxyString);
// ...
std::string greet(std::string_view name,
const Ice::Context& context = Ice::noExplicitContext) const;
std::future<std::string> greetAsync(std::string_view name,
const Ice::Context& context = Ice::noExplicitContext) const;
std::function<void()> greetAsync(std::string_view name,
std::function<void(std::string)> response,
std::function<void(std::exception_ptr)> exception = nullptr,
std::function<void(bool)> sent = nullptr,
const Ice::Context& context = Ice::noExplicitContext) const;
// ...
};
}
Given a proxy to an object of type Greeter, the client can invoke the greet operation as follows:
GreeterPrx greeter{communicator, "greeter:tcp -h localhost -p 4061"};
string greeting = greeter.greet("Alice"); // Get greeting via RPC
This code calls greet on the proxy class instance, which sends the request to the server, waits until the operation is complete, and then unmarshals the return value and returns it to the caller.
Because the return value is of type string, it is safe to ignore the return value. For example, the following code contains no memory leak:
GreeterPrx greeter{communicator, "greeter:tcp -h localhost -p 4061"};
greeter.greet("Alice"); // Useless, but no leak
This is true for all mapped Slice types: you can safely ignore the return value of an operation, no matter what its type — return values are always returned by value. If you ignore the return value, no memory leak occurs because the destructor of the returned value takes care of deallocating memory as needed.
Sync and Async Functions
For each operation, the Slice compiler generates 3 member functions on the proxy class:
one “sync” function with the same name as the operation. When you call this function, your thread waits synchronously until the invocation completes. A successful invocation completes with a return value (which can be void), while an unsuccessful invocation completes with an exception.
two overloaded “async” functions, named
<operation-name>Async. When you call these functions, your thread marshals the arguments to the function synchronously, but the remainder of this invocation is asynchronous, and the function returns immediately. You get the result (return value or exception) through anstd::futureor a callback depending on the async overload you selected. These async functions are described in more detail in Asynchronous Method Invocation (AMI) in C++.
Async invocations allow you to use threads more efficiently. Sync invocations are more convenient to call. You decide what’s more important for your application.
Exception Handling
Any operation invocation may throw a runtime exception and, if the operation has an exception specification, may also throw user exceptions. Suppose we have the following simple interface:
exception Tantrum
{
string reason;
}
interface Child
{
void askToCleanUp() throws Tantrum;
}
Slice exceptions are thrown as C++ exceptions, so you can simply enclose one or more operation invocations in a try-catch block:
ChildPrx child = ...; // Get Child proxy...
try
{
child.askToCleanUp(); // Give it a try...
}
catch (const Tantrum& t)
{
cout << "The child says: " << t.reason << endl;
}