Classes

6 min read

5 min read

5 min read

5 min read

4 min read

3 min read

3 min read

3 min read

3 min read

A class is a user-defined type that holds a list of fields, just like a struct. Classes also offer capabilities not offered by structs:

  • Extensibility You can extend a class through inheritance and optional fields
  • Graph preservation You can transmit a graph of class instances through a Slice operation
  • Null/not-set value A class parameter or field can be null or not-set, whereas a struct parameter or field must have a value
  • Slicing A recipient can unmarshal a class instance into a base class by slicing off derived "slices" it does not know

These extra capabilities are not free: the marshaling/unmarshaling of a class is much more complex and time consuming than the marshaling/unmarshaling of a struct, and its binary representation is larger. As a result, you should only select a class over a struct when these extra capabilities may be useful for your application.

A Slice class is mapped to a C++ class with the same name. The generated class contains a public data member for each Slice field (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;
using TimeOfDayPtr = std::shared_ptr<TimeOfDay>;
class TimeOfDay : public Ice::Value
{
public:
TimeOfDay() noexcept = default;
TimeOfDay(
std::int16_t hour,
std::int16_t minute,
std::int16_t second,
std::string tz) noexcept;
[[nodiscard]] TimeOfDayPtr ice_clone() const;
std::int16_t hour;
std::int16_t minute;
std::int16_t second;
std::string tz;
};

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

  1. The generated class TimeOfDay inherits from Ice::Value. Ice::Value is the ultimate ancestor of all mapped classes.
  2. The generated class contains a public data member for each Slice field.
  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_clone, which returns a shallow polymorphic copy of this class instance.

Classes have two 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.
  • 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 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;
using BasePtr = std::shared_ptr<Base>;
class Derived;
using DerivedPtr = std::shared_ptr<Derived>;
class Base : public Ice::Value
{
public:
Base() noexcept = default;
explicit Base(std::int32_t i) noexcept;
[[nodiscard]] BasePtr ice_clone() const;
std::int32_t i;
};
class Derived : public Base
{
public:
Derived() noexcept = default;
Derived(std::int32_t i, std::string s, std::string greeting) noexcept;
[[nodiscard]] DerivedPtr ice_clone() const;
std::string s;
std::string greeting{"hello"};
};

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

You can print any class instance by calling ice_print on this instance. ice_print is defined on Ice::Value. Alternatively, you can print a shared pointer to a class instance (for example, a TimeOfDayPtr) with operator<<:

C++
TimeOfDayPtr breakTime = ...;
cout << "Taking a break at " << breakTime << endl;

operator<< just calls Value::ice_print when the shared pointer is not null.

You can use the metadata directive "cpp:custom-print" to tell the Slice compiler that you want to use your own custom print implementation. For example:

Slice
["cpp:custom-print"]
class TimeOfDay { ... }

The Slice compiler then generates an ice_print override declaration in the mapped C++ class, and you are responsible to implement this member function.

A Slice class is mapped to a C# class with the same name. By default, the generated class contains a public field for each Slice field (just as for structures and exceptions). Alternatively, you can use the property mapping by specifying the "cs:property" metadata directive, which generates classes with properties instead of fields.

Consider the following class definition:

Slice
class TimeOfDay
{
["cs:identifier:Hour"]
short hour; // 0 - 23
["cs:identifier:Minute"]
short minute; // 0 - 59
["cs:identifier:Second"]
short second; // 0 - 59
["cs:identifier:TZ"]
string tz; // e.g. GMT, PST, EDT...
}

The Slice compiler generates the following code for this definition:

C#
public partial class TimeOfDay : Ice.Value
{
public short Hour;
public short Minute;
public short Second;
public string TZ;
partial void ice_initialize();
public TimeOfDay()
{
...
ice_initialize();
}
public TimeOfDay(short Hour, short Minute, short Second, string TZ)
{
this.Hour = Hour;
this.Minute = Minute;
this.Second = Second;
this.TZ = TZ;
ice_initialize();
}
}

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

  1. The generated class TimeOfDay inherits from Ice.Value. This means that all classes implicitly inherit from Value, which is the ultimate ancestor of all classes.
  2. The generated class contains a public field for each Slice field.
  3. The generated class has a primary constructor and a parameterless constructor.

All generated classes have a public parameterless constructor that initializes all fields using default values (see Fields). This constructor is used by the unmarshaling code. The unmarshaling code guarantees that all non-nullable fields receive a non-null value before the instance is returned to the application code.

A generated class also provides a primary constructor that accepts one argument for each field of the class. This allows you to create and initialize a class in a single statement, for example:

C#
var tod = new TimeOfDay(14, 45, 00, "PST"); // 2:45pm

For a derived class, the primary constructor requires one argument for every field of the class, including inherited fields.

You can instruct the compiler to emit property definitions instead of public fields. For example:

Slice
["cs:property"] class Point
{
["cs:identifier:X"]
double x;
["cs:identifier:Y"]
double y;
}

The "cs:property" metadata directive causes the compiler to generate a property for each Slice field:

C#
public partial class Point : Ice.Value
{
public double X { get; set; }
public double Y { get; set; }
// ...
// same as without cs:property
}

A Slice class is mapped to a Java class with the same name. The generated class contains a public field for each Slice field (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:

Java
public class TimeOfDay extends com.zeroc.Ice.Value {
public TimeOfDay();
public TimeOfDay(short hour, short minute, short second, String tz);
public short hour;
public short minute;
public short second;
public String tz;
public TimeOfDay clone();
...
}

There are a several things to note about the generated code:

  1. The generated class TimeOfDay inherits from com.zeroc.Ice.Value. This means that all classes implicitly inherit from Value, which is the ultimate ancestor of all classes.
  2. The generated class contains a public field for each Slice field.
  3. The generated class has a canonical constructor that takes one argument for each field, as well as a parameterless constructor.

All generated classes have at least two constructors:

  • a canonical constructor that accepts one argument for each field of the class
  • a parameterless constructor that initializes all fields using default values described (see Fields)

When a Slice class declares both optional and non-optional fields, the mapped Java class provides a third constructor that accepts arguments for just the non-optional fields; the optional fields are left unset.

The canonical constructor accepts one argument for each field of the class. This allows you to create and initialize a class in a single statement, for example:

Java
TimeOfDay tod = new TimeOfDay(14, 45, 00, "PST"); // 14:45pm PST

For derived classes, the constructor requires an argument for every field of the class, including inherited members. For example, consider the the definition from Class Inheritance once more:

Slice
class TimeOfDay
{
short hour; // 0 - 23
short minute; // 0 - 59
short second; // 0 - 59
}
class DateTime extends TimeOfDay
{
short day; // 1 - 31
short month; // 1 - 12
short year; // 1753 onwards
}

The constructors for the generated classes are as follows:

Java
public class TimeOfDay extends ... {
public TimeOfDay() {}
public TimeOfDay(short hour, short minute, short second) {
this.hour = hour;
this.minute = minute;
this.second = second;
}
// ...
}
public class DateTime extends TimeOfDay {
public DateTime() {}
public DateTime(
short hour,
short minute,
short second,
short day,
short month,
short year) {
super(hour, minute, second);
this.day = day;
this.month = month;
this.year = year;
}
// ...
}

A Slice class is mapped to a JavaScript class with the same name. For each Slice field, the JavaScript instance contains a corresponding field (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:

JavaScript
// JavaScript generated code.
class TimeOfDay extends Ice.Value {
constructor(hour = 0, minute = 0, second = 0, tz = "") {
super();
this.hour = hour;
this.minute = minute;
this.second = second;
this.tz = tz;
}
}
TypeScript
// TypeScript generated definition.
class TimeOfDay extends Ice.Value {
constructor(hour?: number, minute?: number, second?: number, tz?: string);
hour: number;
minute: number;
second: number;
tz: string;
}

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

  1. The generated TimeOfDay class inherits from Ice.Value.
  2. The generated class provides a constructor that accepts a value for each field.
  3. The generated class defines a JavaScript field for each Slice field.

The generated constructor has one parameter for each field. This allows you to construct and initialize an instance in a single statement (instead of first having to construct the instance and then assign to its fields).

For example:

JavaScript
const tod = new TimeOfDayI(14, 45, 00, "PST"); // 14:45pm PST

All these parameters have also default values (see Fields).

For derived classes, the constructor requires an argument for every field of the class, including inherited fields. For example, consider the the definition from Class Inheritance once more:

Slice
class TimeOfDay
{
short hour; // 0 - 23
short minute; // 0 - 59
short second; // 0 - 59
string tz; // e.g. GMT, PST, EDT...
}
class DateTime extends TimeOfDay
{
short day; // 1 - 31
short month; // 1 - 12
short year; // 1753 onwards
}

The constructors generated for these classes are similar to the following:

JavaScript
class DateTime extends TimeOfDay {
constructor(hour, minute, second, tz, day = 0, month = 0, year = 0) {
super(hour, minute, second, tz);
this.day = day;
this.month = month;
this.year = year;
}
}
TypeScript
class DateTime extends TimeOfDay {
constructor(
hour?: number,
minute?: number,
second?: number,
tz?: string,
day?: number,
month?: number,
year?: number);
day: number;
month: number;
year: number;
}

A Slice class is mapped to a MATLAB class with the same name. The generated class contains a public property for each Slice field (just as for structures and exceptions).

Consider the following class definition:

Slice
class TimeOfDay
{
["matlab:identifier:Hour"]
short hour; // 0 - 23
["matlab:identifier:Minute"]
short minute; // 0 - 59
["matlab:identifier:Second"]
short second; // 0 - 59
["matlab:identifier:TZ"]
string tz; // e.g. GMT, PST, EDT...
}

The Slice compiler generates the following code for this definition:

MATLAB
classdef TimeOfDay < Ice.Value
properties
Hour (1, 1) int16
Minute (1, 1) int16
Second (1, 1) int16
TZ (1, :) char
end
methods
function obj = TimeOfDay(Hour, Minute, Second, TZ)
if nargin > 0
assert(nargin == 4, 'Invalid number of arguments');
% ...
end
end
% ...
end
end

There are several things to note about the generated code:

  1. The generated class TimeOfDay inherits from Ice.Value. This means that all classes implicitly inherit from Value, which is the ultimate ancestor of all classes.
  2. The generated class contains a public property for each Slice field.
  3. The generated class has a constructor that takes one argument for each field.

If a Slice class declares or inherits any field, the generated constructor accepts one parameter for each property so that you can construct and initialize an instance in a single statement (instead of first having to construct the instance and then assign to its properties). For a derived class, the constructor accepts one argument for each base class property, plus one argument for each derived class property, in base-to-derived order.

You must either call the constructor with no arguments or with arguments for all of the parameters.

Calling the constructor with no argument assigns default values to the properties (see Fields).

A Slice class maps to a PHP class with the same name. For each Slice field, the generated class contains a public variable, 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
}

The PHP mapping generates the following code for this definition:

PHP
class TimeOfDay extends \Ice\Value
{
public $hour;
public $minute;
public $second;
public function __construct($hour=0, $minute=0, $second=0)
{
$this->hour = $hour;
$this->minute = $minute;
$this->second = $second;
}
}

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

  1. The generated class TimeOfDay inherits from \Ice\Value. This reflects the semantics of Slice classes in that all classes implicitly inherit from \Ice\Value, which is the ultimate ancestor of all classes.
  2. The constructor initializes an instance variable for each Slice field.

The generated constructor has one parameter for each field. This allows you to construct and initialize an instance in a single statement (instead of first having to construct the instance and then assign to its variables).

All these parameters have also default values (see Fields).

For derived classes, the constructor has one parameter for each of the base class's fields, plus one parameter for each of the derived class's fields, in base-to-derived order.

A Slice class maps to a Python dataclass with the same name. The generated class contains a field for each Slice field (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
}

The Python mapping generates the following code for this definition:

Python
@dataclass(eq=False)
class TimeOfDay(Value):
hour: int = 0
minute: int = 0
second: int = 0
tz: str = ""
# ...

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.

All mapped fields have default values, such as 0 and the empty string (see Fields for details).

The mapped dataclass is configured with eq=False to provide reference-equality semantics like in other language mappings: two class instances are equal only when they are actually the same instance.

A Slice class maps to a Ruby class with the same name. For each Slice field, the generated class contains an instance variable and accessors to read and write it, 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
}

The Ruby mapping generates the following code for this definition:

Ruby
class TimeOfDay < ::Ice::Value
attr_accessor :hours, :minutes, :seconds
def initialize(hour=0, minute=0, second=0)
@hour = hour
@minute = minute
@second = second
end
end

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

  1. The generated class TimeOfDay derives from Ice::Value. This reflects the semantics of Slice classes in that all classes implicitly inherit from Ice::Value, which is the ultimate ancestor of all classes.
  2. The constructor defines an instance variable for each Slice field.

The generated constructor has one parameter for each field. This allows you to construct and initialize an instance in a single statement (instead of first having to construct the instance and then assign to its variables).

All these parameters have also default values (see Fields).

For derived classes, the constructor has one parameter for each of the base class's fields, plus one parameter for each of the derived class's fields, in base-to-derived order.

A Slice class is mapped to an open Swift class with the same name. The generated class contains a public stored property for each Slice field (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:

Swift
open class TimeOfDay: Ice.Value {
public var hour: Int16 = 0
public var minute: Int16 = 0
public var second: Int16 = 0
public var tz: String = ""
public required init() {}
public init(hour: Int16, minute: Int16, second: Int16, tz: String) {
self.hour = hour
self.minute = minute
self.second = second
self.tz = tz
}
...
}

There are a several things to note about the generated code:

  1. The generated class TimeOfDay inherits from class Ice.Value. Value is the ultimate ancestor of all classes.
  2. The generated class contains a public stored property for each Slice field.
  3. The generated class provides a default initializer and a memberwise initializer. The default initializer initializes all stored properties to zero, nil or empty, as appropriate. See Fields for details.