enum Fruit { Apple, Pear, Orange };
enum Fruit { Apple, Pear, Orange };
Slice structures map to C++ structures with the same name. For each Slice data member, the C++ structure contains a public data member. For example, here is our
Employee structure from
Section 4.9.4 once more:
struct Employee {
long number;
string firstName;
string lastName;
};
struct Employee {
Ice::Long number;
std::string firstName;
std::string lastName;
bool operator==(const Employee&) const;
bool operator!=(const Employee&) const;
bool operator<(const Employee&) const;
bool operator<=(const Employee&) const;
bool operator>(const Employee&) const;
bool operator>=(const Employee&) const;
};
bool Employee::operator<(const Employee& rhs) const
{
if (this == &rhs) // Short‑cut self‑comparison
return false;
// Compare first members
//
if (number < rhs.number)
return true;
else if (rhs.number < number)
return false;
// First members are equal, compare second members
//
if (firstName < rhs.firstName)
return true;
else if (rhs.firstName < firstName)
return false;
// Second members are equal, compare third members
//
if (lastName < rhs.lastName)
return true;
else if (rhs.lastName < lastName)
return false;
// All members are equal, so return false
return false;
}
Note that copy construction and assignment always have deep-copy semantics. You can freely assign structures or structure members to each other without having to worry about memory management. The following code fragment illustrates both comparison and deep-copy semantics:
Employee e1, e2;
e1.firstName = "Bjarne";
e1.lastName = "Stroustrup";
e2 = e1; // Deep copy
assert(e1 == e2);
e2.firstName = "Andrew"; // Deep copy
e2.lastName = "Koenig"; // Deep copy
assert(e2 < e1);
Because strings are mapped to std::string, there are no memory management issues in this code and structure assignment and copying work as expected. (The default member-wise copy constructor and assignment operator generated by the C++ compiler do the right thing.)
Occasionally, the mapping of Slice structures to C++ structures can be inefficient. For example, you may need to pass structures around in your application, but want to avoid having to make expensive copies of the structures. (This overhead becomes noticeable for structures with many complex data members, such as sequences or strings.) Of course, you could pass the structures by const reference, but that can create its own share of problems, such as tracking the life time of the structures to avoid ending up with dangling references.
For this reason, you can enable an alternate mapping that maps Slice structures to C++ classes. Classes (as opposed to structures) are reference-counted. Because the Ice C++ mapping provides smart pointers for classes (see
Section 6.14.6), you can keep references to a class instance in many places in the code without having to worry about either expensive copying or life time issues.
The alternate mapping is enabled by a metadata directive, ["cpp:class"]. Here is our Employee structure once again, but this time with the additional metadata directive:
["cpp:class"] struct Employee {
long number;
string firstName;
string lastName;
};
class Employee : public IceUtil::Shared {
public:
Employee() {}
Employee(::Ice::Long,
const ::std::string&,
const ::std::string&);
::Ice::Long number;
::std::string firstName;
::std::string lastName;
bool operator==(const Employee&) const;
bool operator!=(const Employee&) const;
bool operator<(const Employee&) const;
bool operator<=(const Employee&) const;
bool operator>(const Employee&) const;
bool operator>=(const Employee&) const;
};
Note that the generated class, apart from a default constructor, has a constructor that accepts one argument for each member of the structure. This allows you to instantiate and initialize the class in a single statement (instead of having to first instantiate the class and then assign to its members).
For details on how to instantiate classes, and how to access them via smart pointers, please
Section 6.14—the class mapping described there applies equally to Slice structures that are mapped to classes.
Structures have a default constructor that default-constructs each data member. Members having a complex type, such as strings, sequences, and dictionaries, are initialized by their own default constructor. However, the default constructor performs no initialization for members having one of the simple built‑in types boolean, integer, floating point, or enumeration. For such a member, it is not safe to assume that the member has a reasonable default value. This is especially true for enumerated types as the member’s default value may be outside the legal range for the enumeration, in which case an exception will occur during marshaling unless the member is explicitly set to a legal value.
If you wish to ensure that data members of primitive types are initialized to reasonable values, you can declare default values in your Slice definition (see
Section 4.9.2). The default constructor initializes each of these data members to its declared value.
Structures also have a second constructor that has one parameter for each data member. This allows you to construct and initialize a class instance in a single statement (instead of first having to construct the instance and then assigning to its members).
typedef std::vector<Fruit> FruitPlatter;
// Make a small platter with one Apple and one Orange
//
FruitPlatter p;
p.push_back(Apple);
p.push_back(Orange);
[["cpp:include:list"]]
module Food {
enum Fruit { Apple, Pear, Orange };
["cpp:type:std::list< ::Food::Fruit>"]
sequence<Fruit> FruitPlatter;
};
#include <list>
namespace Food {
typedef std::list< Food::Fruit> FruitPlatter;
// ...
}
The cpp:type metadata directive must be applied to a sequence definition; anything following the
cpp:type: prefix is taken to be the name of the type. For example, we could use
["cpp:type:::std::list< ::Food::Fruit>"]. In that case, the compiler would use a fully-qualified name to define the type:
typedef ::std::list< ::Food::Fruit> FruitPlatter;
Note that the code generator inserts whatever string you specify following the cpp:type: prefix literally into the generated code. This means that, to avoid C++ compilation failures due to unknown symbols, you should use a qualified name for the type.
Also note that, to avoid compilation errors in the generated code, you must instruct the compiler to generate an appropriate include directive with the
cpp:include global metadata directive. This causes the compiler to add the line
Instead of std::list, you can specify a type of your own as the sequence type, for example:
With these metadata directives, the compiler will use a C++ type FruitBowl as the sequence type, and add an include directive for the header file
FruitBowl.h to the generated code.
You can use any class of your choice as a sequence type, but the class must meet certain requirements. (
vector,
list, and
deque happen to meet these requirements.)
•
The class must define iterator and
const_iterator types and must provide
begin and
end member functions with the usual semantics; the iterators must be comparable for equality and inequality.
Less formally, this means that if the class looks like a vector,
list, or
deque with respect to these points, you can use it as a custom sequence implementation.
[["cpp:include:list"]]
[["cpp:include:deque"]]
module Food {
enum Fruit { Apple, Pear, Orange };
sequence<Fruit> FruitPlatter;
interface Market {
["cpp:type:list< ::Food::Fruit>"]
FruitPlatter
barter(
["cpp:type:deque< ::Food::Fruit>"] FruitPlatter offer
);
};
};
With this definition, the default mapping of FruitPlatter to a C++
vector still applies but the return value of
barter is mapped as a
list, and the
offer parameter is mapped as a
deque.
interface File {
void write(["cpp:array"] Ice::ByteSeq contents);
};
The cpp:array metadata directive instructs the compiler to map the
contents parameter to a pair of pointers. With this directive, the
write method on the proxy has the following signature:
void write(const std::pair<const Ice::Byte*,
const Ice::Byte*>& contents);
To pass a byte sequence to the server, you pass a pair of pointers; the first pointer points at the beginning of the sequence, and the second point points one element past the end of the sequence.
Similarly, for the server side, the write method on the skeleton has the following signature:
virtual void write(const ::std::pair<const ::Ice::Byte*,
const ::Ice::Byte*>&,
const ::Ice::Current& = ::Ice::Current()) = 0;
The array mapping is useful to achieve zero-copy passing of sequences. The pointers point directly into the server-side transport buffer; this allows the server-side run time to avoid creating a
vector to pass to the operation implementation, thereby avoiding both allocating memory for the sequence and copying its contents into that memory.
Note that you can use the array mapping for any sequence type. However, it provides a performance advantage only for byte sequences (on all platforms) and for sequences of integral type (x86 platforms only).
Also note that the called operation in the server must not store a pointer into the passed sequence because the transport buffer into which the pointer points is deallocated as soon as the operation completes.
interface File {
void write(["cpp:range"] Ice::ByteSeq contents);
};
The cpp:range metadata directive instructs the compiler to map the
contents parameter to a pair of
const_iterator. With this directive, the
write method on the proxy has the following signature:
void write(const std::pair<Ice::ByteSeq::const_iterator,
Ice::ByteSeq::const_iterator>& contents);
Similarly, for the server side, the write method on the skeleton has the following signature:
virtual void write(const ::std::pair<
::Ice::ByteSeq::const_iterator,
::Ice::ByteSeq::const_iterator>&,
const ::Ice::Current& = ::Ice::Current()) = 0;
The motivation for the range mapping is the same as for the array mapping: the passed iterators point directly into the server-side transport buffer and so avoid the need to create a temporary
vector to pass to the operation.
As for the array mapping, the range mapping can be used with any sequence type, but offers a performance advantage only for byte sequences (on all platforms) and for sequences of integral type (x86 platforms only).
The operation must not store an iterator into the passed sequence because the transport buffer into which the iterator points is deallocated as soon as the operation completes.
You can optionally add a type name to the cpp:range metadata directive, for example:
interface File {
void write(
["cpp:range:std::deque<Ice::Byte>"]
Ice::ByteSeq contents);
};
virtual void write(const ::std::pair<
std::deque<Ice::Byte>::const_iterator,
std::deque<Ice::Byte>::const_iterator>&,
const ::Ice::Current& = ::Ice::Current()) = 0;
typedef std::map<Ice::Long, Employee> EmployeeMap;
Again, there are no surprises here: a Slice dictionary simply maps to an STL map. As a result, you can use the dictionary like any other STL
map, for example:
EmployeeMap em;
Employee e;
e.number = 42;
e.firstName = "Stan";
e.lastName = "Lippman";
em[e.number] = e;
e.number = 77;
e.firstName = "Herb";
e.lastName = "Sutter";
em[e.number] = e;