C++11 Mapping for Classes

On this page:

Basic C++ Mapping for Classes

A Slice class is mapped to a C++ class with the same name. The generated class contains a public data member for each Slice data member (just as for structures and exceptions). Consider the following class definition:

Slice
class TimeOfDay
{
    short hour;         // 0 - 23
    short minute;       // 0 - 59
    short second;       // 0 - 59
    string tz;          // e.g. GMT, PST, EDT...
}

The Slice compiler generates the following code for this definition:

C++
class TimeOfDay : public Ice::Value
{
public:
    short hour;
    short minute;
    short second;
    std::string tz;

    TimeOfDay() = default;
    TimeOfDay(short, short, short, string);

    virtual std::shared_ptr<Ice::Value> ice_clone() const;
 
	std::tuple<const short&, const short&, const short&, const ::std::string&> ice_tuple() const;
 
	static const std::string& ice_staticId();
 
protected:
    // ...
};

There are a number of things to note about this generated code:

  1. The generated class TimeOfDay inherits from Ice::Value. This means that all classes implicitly inherit from Ice::Value which is the ultimate ancestor of all classes.
  2. The generated class contains a public member for each Slice data member.
  3. The generated class has a constructor that takes one argument for each data member, as well as a default constructor.
  4. The generated class has a function, ice_tuple, which returns a std::tuple of the class' data members. Ice uses this tuple to perform TimeOfDay class comparison.

There is quite a bit to discuss here, so we will look at each item in turn.

Inheritance from Ice::Value

Classes implicitly inherit from a common base class, Value, which is mapped to Ice::Value in C++.

Ice::Value is a very simple base class with just a few member functions:

C++
namespace Ice
{
    class Value
    {
    public:
        virtual ~Value() = default;
    
        virtual void ice_preMarshal();
        virtual void ice_postUnmarshal();
 
        virtual std::shared_ptr<Value> ice_clone() const;
 
		virtual std::shared_ptr<SlicedData> ice_getSlicedData() const;
   
        static const std::string& ice_staticId();
        ...
    };
}

The member functions of Ice::Value behave as follows:

  • ice_preMarshal
    The Ice run time invokes this function prior to marshaling the object's state, providing the opportunity for a subclass to validate its declared data members.
  • ice_postUnmarshal
    The Ice run time invokes this function after unmarshaling an object's state. A subclass typically overrides this function when it needs to perform additional initialization using the values of its declared data members.
  • ice_clone
    This function returns a shared_ptr that holds a shallow copy of this value.
  • ice_getSlicedData
    This functions returns the SlicedData object if the value has been sliced during un-marshaling or nullptr otherwise.
  • ice_staticId
    This function returns the static type ID of a class.

Class Data Members in C++

By default, data members of classes are mapped exactly as for structures and exceptions: for each data member in the Slice definition, the generated class contains a corresponding public data member. Optional data members are mapped to instances of the Ice::optional template.

If you wish to restrict access to a data member, you can modify its visibility using the protected metadata directive. The presence of this directive causes the Slice compiler to generate the data member with protected visibility. As a result, the member can be accessed only by the class itself or by one of its subclasses. For example, the TimeOfDay class shown below has the protected metadata directive applied to each of its data members:

Slice
class TimeOfDay
{
    ["protected"] short hour;   // 0 - 23
    ["protected"] short minute; // 0 - 59
    ["protected"] short second; // 0 - 59
    ["protected"] string tz;    // GMT, EST etc.
}

The Slice compiler produces the following generated code for this definition:

C++
class TimeOfDay : public Ice::Value
{
public:
    // ctors etc.

protected:

    short hour;
    short minute;
    short second;
    std::string tz;
};

For a class in which all of the data members are protected, the metadata directive can be applied to the class itself rather than to each member individually. For example, we can rewrite the TimeOfDay class as follows:

Slice
["protected"] class TimeOfDay
{
    short hour;         // 0 - 23
    short minute;       // 0 - 59
    short second;       // 0 - 59
    string tz; 
}

Class Constructors with C++

Classes have several constructors:

  • a default constructor that default-constructs each data member
    This default constructor is no-op and implemented as = default. 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.
    To ensure that data members of primitive types are initialized to reasonable values, you can declare default values in your Slice definition, and the Slice compiler will generate data member initializers for the corresponding C++ data members. Optional data members are unset unless they declare default values.
  • a constructor with one parameter for each data member (the one-shot constructor)
    This constructor allows you to construct and initialize a class instance in a single statement. For each optional data member, its corresponding constructor parameter uses the same mapping as for operation parameters, allowing you to pass its initial value or Ice::nullopt to indicate an unset value. Each parameter is passed by value: each movable parameter is moved into the corresponding data member, while other parameters are copied.
  • a copy constructor
  • a move constructor

For derived classes, the one-shot constructor has one parameter for each of the base class's data members, plus one parameter for each of the derived class's data members, in base-to-derived order. For example:

Slice
class Base 
{
    int i;
}

class Derived extends Base
{
    string s;
    string greeting = "hello";
}

This generates:

C++
class Base : public Ice::Value
{
public:
    int i;

    Base() = default;
    explicit Base(int) { ... }
    Base(const Base&);
    Base(Base&&);
    // ...
};

class Derived : public Base
{
public:
    std::string s;
    std::string greeting = "hello";

    Derived() = default;
    Derived(int, string, string); // one-shot ctor
    Derived(const Derived&&);
	Derived(Derived&&);

    // ...
};

Note that single-parameter constructors are defined as explicit, to prevent implicit argument conversions.

Value Factories in C++

Value factories allow you to create classes derived from the C++ class generated by the Slice compiler, and tell the Ice run time to create instances of these classes when unmarshaling. For example, with the following simple interface:

Slice
interface Time
{
    TimeOfDay get();
}

The Ice run time will by default create and return a plain TimeOfDay instance.

If you wish, you can create your own custom derived class, and tell Ice to create and return these instances instead. For example:

C++
class CustomTimeOfDay : public TimeOfDay 
{
public:
    std::string format() { ... prints formatted data members ... }
};

You then create and register a value factory for your custom class with your Ice communicator:

C++
auto communicator = ...;
communicator->getValueFactoryManager()->add(
    [](const string& type) 
    { 
        assert(type == TimeOfDay::ice_staticId());
        return new CustomTimeOfDay;
    },                
    TimeOfDay::ice_staticId()));

Class with Operations in C++

Deprecated Feature

Operations on classes are deprecated as of Ice 3.7. Skip this section unless you need to communicate with old applications that rely on this feature.

Operations on classes are not mapped at all into the corresponding C++ class. The generated C++ class is the same whether the Slice class has operations or not, except for the nested Result structs described later in this section.

The Slice to C++ compiler also generates a separate <class-name>Disp class, which can be used to implement an Ice object with these operations. For example:

Slice
class FormattedTimeOfDay
{
    short hour;         // 0 - 23
    short minute;       // 0 - 59
    short second;       // 0 - 59
    string tz;
    string format();
}

results in the following generated code:

C++
class FormattedTimeOfDay : public Ice::Value 
{
     // ... operation format() not mapped at all here
};

// Disp class for servant implementation
class FormattedTimeOfDayDisp : public virtual Ice::Object
{
public:
     virtual std::string format(const Ice::Current& = Ice::noExplicitCurrent) = 0;
     // ...
};

This Disp class is the C++ skeleton class for this Slice class. Skeleton classes are described in the Server-Side C++ Mapping for Interfaces.

Like skeleton classes for interfaces, the generated C++ Disp classes always derive from their base class(es) virtually. This virtual inheritance allows the reuse of implementation-classes through multiple inheritance.

If an operation has a return value and one or more out parameters, or no return value and two or more out parameters, the generated C++ class provides a nested C++ struct used for asynchronous calls. For example:

Slice
class XYZ 
{
    int x;
    string op(out int y);
}

results in the following generated code:

C++
class XYZ : public Ice::Value
{
public:
    
    int x;
    struct OpResult 
    {
       string returnValue; 
       int y; 
    };
};

// Disp class for servant implementation
class XYZDisp : public virtual Ice::Object
{
public:
     virtual std::string op(int&, const Ice::Current& = Ice::noExplicitCurrent) = 0;
     // ...
};

See Also