As we saw in Section 6.11, for each operation on an interface, the proxy class contains a corresponding member function with the same name. To invoke an operation, you call it via the proxy handle. For example, here is part of the definitions for our file system from
Section 5.4:
module Filesystem {
interface Node {
idempotent string name();
};
// ...
};
The proxy class for the Node interface, tidied up to remove irrelevant detail, is as follows:
namespace IceProxy {
namespace Filesystem {
class Node : virtual public IceProxy::Ice::Object {
public:
std::string name();
// ...
};
typedef IceInternal::ProxyHandle<Node> NodePrx;
// ...
}
// ...
}
The name operation returns a value of type
string. Given a proxy to an object of type
Node, the client can invoke the operation as follows:
NodePrx node = ...; // Initialize proxy
string name = node‑>name(); // Get name via RPC
The proxy handle overloads operator‑> to forward method calls to the underlying proxy class instance which, in turn, sends the operation invocation 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:
NodePrx node = ...; // Initialize proxy
node‑>name(); // 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.
6.12.1 Normal and idempotent Operations
You can add an idempotent qualifier to a Slice operation. As far as the signature for the corresponding proxy methods is concerned,
idempotent has no effect. For example, consider the following interface:
interface Example {
string op1();
idempotent string op2();
idempotent void op3(string s);
};
namespace IceProxy {
class Example : virtual public IceProxy::Ice::Object {
public:
std::string op1();
std::string op2(); // idempotent
void op3(const std::string&); // idempotent
// ...
};
}
Because idempotent affects an aspect of call dispatch, not interface, it makes sense for the mapping to be unaffected by the
idempotent keyword.
The parameter passing rules for the C++ mapping are very simple: parameters are passed either by value (for small values) or by
const reference (for values that are larger than a machine word). Semantically, the two ways of passing parameters are identical: it is guaranteed that the value of a parameter will not be changed by the invocation (with some caveats—see
page 217 and
page 1090).
struct NumberAndString {
int x;
string str;
};
sequence<string> StringSeq;
dictionary<long, StringSeq> StringTable;
interface ClientToServer {
void op1(int i, float f, bool b, string s);
void op2(NumberAndString ns, StringSeq ss, StringTable st);
void op3(ClientToServer* proxy);
};
struct NumberAndString {
Ice::Int x;
std::string str;
// ...
};
typedef std::vector<std::string> StringSeq;
typedef std::map<Ice::Long, StringSeq> StringTable;
namespace IceProxy {
class ClientToServer : virtual public IceProxy::Ice::Object {
public:
void op1(Ice::Int, Ice::Float, bool, const std::string&);
void op2(const NumberAndString&,
const StringSeq&,
const StringTable&);
void op3(const ClientToServerPrx&);
// ...
};
}
Given a proxy to a ClientToServer interface, the client code can pass parameters as in the following example:
ClientToServerPrx p = ...; // Get proxy...
p‑>op1(42, 3.14, true, "Hello world!"); // Pass simple literals
int i = 42;
float f = 3.14;
bool b = true;
string s = "Hello world!";
p‑>op1(i, f, b, s); // Pass simple variables
NumberAndString ns = { 42, "The Answer" };
StringSeq ss;
ss.push_back("Hello world!");
StringTable st;
st[0] = ss;
p‑>op2(ns, ss, st); // Pass complex variables
p‑>op3(p); // Pass proxy
You can pass either literals or variables to the various operations. Because everything is passed by value or
const reference, there are no memory-management issues to consider.
struct NumberAndString {
int x;
string str;
};
sequence<string> StringSeq;
dictionary<long, StringSeq> StringTable;
interface ServerToClient {
void op1(out int i, out float f, out bool b, out string s);
void op2(out NumberAndString ns,
out StringSeq ss,
out StringTable st);
void op3(out ServerToClient* proxy);
};
namespace IceProxy {
class ServerToClient : virtual public IceProxy::Ice::Object {
public:
void op1(Ice::Int&, Ice::Float&, bool&, std::string&);
void op2(NumberAndString&, StringSeq&, StringTable&);
void op3(ServerToClientPrx&);
// ...
};
}
Given a proxy to a ServerToClient interface, the client code can pass parameters as in the following example:
ServerToClientPrx p = ...; // Get proxy...
int i;
float f;
bool b;
string s;
p‑>op1(i, f, b, s);
// i, f, b, and s contain updated values now
NumberAndString ns;
StringSeq ss;
StringTable st;
p‑>op2(ns, ss, st);
// ns, ss, and st contain updated values now
p‑>op3(p);
// p has changed now!
Again, there are no surprises in this code: the caller simply passes variables to an operation; once the operation completes, the values of those variables will be set by the server.
p‑>op3(p); // Weird, but well‑defined
Here, p is the proxy that is used to dispatch the call. That same variable
p is also passed as an out-parameter to the call, meaning that the server will set its value. In general, passing the same parameter as both an input and output parameter is safe: the Ice run time will correctly handle all locking and memory-management activities.
sequence<int> Row;
sequence<Row> Matrix;
interface MatrixArithmetic {
void multiply(Matrix m1,
Matrix m2,
out Matrix result);
};
Given a proxy to a MatrixArithmetic interface, the client code could do the following:
MatrixArithmeticPrx ma = ...; // Get proxy...
Matrix m1 = ...; // Initialize one matrix
Matrix m2 = ...; // Initialize second matrix
ma‑>squareAndCubeRoot(m1, m2, m1); // !!!
This code is technically legal, in the sense that no memory corruption or locking issues will arise, but it has surprising behavior: because the same variable
m1 is passed as an input parameter as well as an output parameter, the final value of
m1 is indeterminate—in particular, if client and server are collocated in the same address space, the implementation of the operation will overwrite parts of the input matrix
m1 in the process of computing the result because the result is written to the same physical memory location as one of the inputs. In general, you should take care when passing the same variable as both an input and output parameter and only do so if the called operation guarantees to be well-behaved in this case.
interface Name {
string getName();
void setName(string name);
};
Suppose we have two proxies to interfaces of type Name,
p1 and
p2, and chain invocations as follows:
p2‑>setName(p1‑>getName());
This works exactly as intended: the value returned by p1 is transferred to
p2. There are no memory-management or exception safety issues with this code.