Table of Contents Previous Next
Logo
Client-Side Slice-to-C++ Mapping : 6.14 Mapping for Classes
Copyright © 2003-2009 ZeroC, Inc.

6.14 Mapping for Classes

Slice classes are mapped to C++ classes with the same name. The generated class contains a public data member for each Slice data member, and a virtual member function for each operation. Consider the following class definition:
class TimeOfDay {
    short hour;         // 0  23
    short minute;       // 0  59
    short second;       // 0  59
    string format();    // Return time as hh:mm:ss
};
The Slice compiler generates the following code for this definition:1
class TimeOfDay;

typedef IceInternal::ProxyHandle<IceProxy::TimeOfDay> TimeOfDayPrx;
typedef IceInternal::Handle<TimeOfDay> TimeOfDayPtr;

class TimeOfDay : virtual public Ice::Object {
public:
    Ice::Short hour;
    Ice::Short minute;
    Ice::Short second;

    virtual std::string format() = 0;

    TimeOfDay() {};
    TimeOfDay(Ice::Short, Ice::Short, Ice::Short);

    virtual bool ice_isA(const std::string&);
    virtual const std::string& ice_id();
    static const std::string& ice_staticId();

    typedef TimeOfDayPrx ProxyType;
    typedef TimeOfDayPtr PointerType;

    // ...
};
There are a number of things to note about the generated code:
1. The generated class TimeOfDay inherits from Ice::Object. This means that all classes implicitly inherit from Ice::Object, which is the ultimate ancestor of all classes. Note that Ice::Object is not the same as IceProxy::Ice::Object. In other words, you cannot pass a class where a proxy is expected and vice versa. (However, you can pass a proxy for the class—see Section 6.14.6.)
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 contains a pure virtual member function for each Slice operation.
5. The generated class contains additional member functions: ice_isA, ice_id, ice_staticId, and ice_factory.
6. The compiler generates a type definition TimeOfDayPtr. This type implements a smart pointer that wraps dynamically-allocated instances of the class. In general, the name of this type is <classname>Ptr. Do not confuse this with <classname>Prx—that type exists as well, but is the proxy handle for the class, not a smart pointer.
There is quite a bit to discuss here, so we will look at each item in turn.

6.14.1 Inheritance from Ice::Object

Like interfaces, classes implicitly inherit from a common base class, Ice::Object. However, as shown in Figure 6.1, classes inherit from Ice::Object instead of Ice::ObjectPrx (which is at the base of the inheritance hierarchy for proxies). As a result, you cannot pass a class where a proxy is expected (and vice versa) because the base types for classes and proxies are not compatible.
Figure 6.1. Inheritance from Ice::ObjectPrx and Ice::Object.
Ice::Object contains a number of member functions:
namespace Ice {
    class Object : public IceInternal::GCShared {
    public:
        virtual bool ice_isA(const std::string&,
                             const Current& = Current()) const;
        virtual void ice_ping(const Current&  = Current()) const;
        virtual std::vector<std::string> ice_ids(
                             const Current& = Current()) const;
        virtual const std::string& ice_id(
                             const Current& = Current()) const;
        static const std::string& ice_staticId();
        virtual Ice::Int ice_hash() const;
        virtual ObjectPtr ice_clone() const;

        virtual void ice_preMarshal();
        virtual void ice_postUnmarshal();

        virtual DispatchStatus ice_dispatch(
                  Ice::Request&,
                  const DispatchInterceptorAsyncCallbackPtr& = 0);

        virtual bool operator==(const Object&) const;
        virtual bool operator!=(const Object&) const;
        virtual bool operator<(const Object&) const;
        virtual bool operator<=(const Object&) const;
        virtual bool operator>(const Object&) const;
        virtual bool operator>=(const Object&) const;
    };
}
The member functions of Ice::Object behave as follows:
• ice_isA
This function returns true if the object supports the given type ID, and false otherwise.
• ice_ping
As for interfaces, ice_ping provides a basic reachability test for the class.
• ice_ids
This function returns a string sequence representing all of the type IDs supported by this object, including ::Ice::Object.
• ice_id
This function returns the actual run-time type ID for a class. If you call ice_id through a smart pointer to a base instance, the returned type id is the actual (possibly more derived) type ID of the instance.
• ice_staticId
This function returns the static type ID of a class.
• ice_hash
This method returns a hash value for the class, allowing you to easily place classes into hash tables.
• ice_clone
This function makes a polymorphic shallow copy of a class (see page 250).
• 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_dispatch
This function dispatches an incoming request to a servant. It is used in the implementation of dispatch interceptors (see Section 32.24).
• operator==
operator!=
operator<
operator<=
operator>
operator>=
The comparison operators permit you to use classes as elements of STL sorted containers. Note that sort order, unlike for structures (see Section 6.12), is based on the memory address of the class, not on the contents of its data members of the class.

6.14.2 Data Members of Classes

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.
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:
class TimeOfDay {
    ["protected"] short hour;   // 0  23
    ["protected"] short minute; // 0  59
    ["protected"] short second; // 0  59
    string format();    // Return time as hh:mm:ss
};
The Slice compiler produces the following generated code for this definition:
class TimeOfDay : virtual public Ice::Object {
public:

    virtual std::string format() = 0;

    // ...

protected:

    Ice::Short hour;
    Ice::Short minute;
    Ice::Short second;
};
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:
["protected"] class TimeOfDay {
    short hour;         // 0  23
    short minute;       // 0  59
    short second;       // 0  59
    string format();    // Return time as hh:mm:ss
};

6.14.3 Class Constructors

Classes have a default constructor. This constructor default-constructs each data member. This means that, for members of simple built‑in type, such as integers, the default constructor performs no initialization, whereas members of complex type, such as strings, sequences, and dictionaries, are initialized by their own default constructor.
In addition, classes 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).
For derived classes, the 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:
class Base {
    int i;
};

class Derived extends Base {
    string s;
};
This generates:
class Base : virtual public ::Ice::Object
{
public:
    ::Ice::Int i;

    Base() {};
    explicit Base(::Ice::Int);

    // ...
};

class Derived : virtual public Base
{
public:
    ::std::string s;

    Derived() {};
    Derived(::Ice::Int, const ::std::string&);

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

6.14.4 Operations of Classes

Operations of classes are mapped to pure virtual member functions in the generated class. This means that, if a class contains operations (such as the format operation of our TimeOfDay class), you must provide an implementation of the operation in a class that is derived from the generated class. For example:2
class TimeOfDayI : virtual public TimeOfDay {
public:
    virtual std::string format() {
        std::ostringstream s;
        s << setw(2) << setfill('0') << hour << ":";
        s << setw(2) << setfill('0') << minute << ":";
        s << setw(2) << setfill('0') << second;
        return s.c_str();
    }

protected:
    virtual ~TimeOfDayI() {}  // Optional
};

6.14.5 Class Factories

Having created a class such as TimeOfDayI, we have an implementation and we can instantiate the TimeOfDayI class, but we cannot receive it as the return value or as an out-parameter from an operation invocation. To see why, consider the following simple interface:
interface Time {
    TimeOfDay get();
};
When a client invokes the get operation, the Ice run time must instantiate and return an instance of the TimeOfDay class. However, TimeOfDay is an abstract class that cannot be instantiated. Unless we tell it, the Ice run time cannot magically know that we have created a TimeOfDayI class that implements the abstract format operation of the TimeOfDay abstract class. In other words, we must provide the Ice run time with a factory that knows that the TimeOfDay abstract class has a TimeOfDayI concrete implementation. The Ice::Communicator interface provides us with the necessary operations:
module Ice {
    local interface ObjectFactory {
        Object create(string type);
        void destroy();
    };

    local interface Communicator {
        void addObjectFactory(ObjectFactory factory, string id);
        ObjectFactory findObjectFactory(string id);
        // ...
    };
};
To supply the Ice run time with a factory for our TimeOfDayI class, we must implement the ObjectFactory interface:
module Ice {
    local interface ObjectFactory {
        Object create(string type);
        void destroy();
    };
};
The object factory’s create operation is called by the Ice run time when it needs to instantiate a TimeOfDay class. The factory’s destroy operation is called by the Ice run time when its communicator is destroyed. A possible implementation of our object factory is:
class ObjectFactory : public Ice::ObjectFactory {
public:
    virtual Ice::ObjectPtr create(const std::string& type) {
        assert(type == M::TimeOfDay::ice_staticId());
        return new TimeOfDayI;
    }
    virtual void destroy() {}
};
The create method is passed the type ID (see Section 4.13) of the class to instantiate. For our TimeOfDay class, the type ID is "::M::TimeOfDay". Our implementation of create checks the type ID: if it matches, the method instantiates and returns a TimeOfDayI object. For other type IDs, the method asserts because it does not know how to instantiate other types of objects.
Note that we used the ice_staticId method to obtain the type ID rather than embedding a literal string. Using a literal type ID string in your code is discouraged because it can lead to errors that are only detected at run time. For example, if a Slice class or one of its enclosing modules is renamed and the literal string is not changed accordingly, a receiver will fail to unmarshal the object and the Ice run time will raise NoObjectFactoryException. By using ice_staticId instead, we avoid any risk of a misspelled or obsolete type ID, and we can discover at compile time if a Slice class or module has been renamed.
Given a factory implementation, such as our ObjectFactory, we must inform the Ice run time of the existence of the factory:
Ice::CommunicatorPtr ic = ...;
ic>addObjectFactory(new ObjectFactory,
                     M::TimeOfDay::ice_staticId());
Now, whenever the Ice run time needs to instantiate a class with the type ID "::M::TimeOfDay", it calls the create method of the registered ObjectFactory instance.
The destroy operation of the object factory is invoked by the Ice run time when the communicator is destroyed. This gives you a chance to clean up any resources that may be used by your factory. Do not call destroy on the factory while it is registered with the communicator—if you do, the Ice run time has no idea that this has happened and, depending on what your destroy implementation is doing, may cause undefined behavior when the Ice run time tries to next use the factory.
The run time guarantees that destroy will be the last call made on the factory, that is, create will not be called concurrently with destroy, and create will not be called once destroy has been called. However, calls to create can be made concurrently.
Note that you cannot register a factory for the same type ID twice: if you call addObjectFactory with a type ID for which a factory is registered, the Ice run time throws an AlreadyRegisteredException.
Finally, keep in mind that if a class has only data members, but no operations, you need not create and register an object factory to transmit instances of such a class. Only if a class has operations do you have to define and register an object factory.

6.14.6 Smart Pointers for Classes

A recurring theme for C++ programmers is the need to deal with memory allocations and deallocations in their programs. The difficulty of doing so is well known: in the face of exceptions, multiple return paths from functions, and callee-allocated memory that must be deallocated by the caller, it can be extremely difficult to ensure that a program does not leak resources. This is particularly important in multi-threaded programs: if you do not rigorously track ownership of dynamic memory, a thread may delete memory that is still used by another thread, usually with disastrous consequences.
To alleviate this problem, Ice provides smart pointers for classes. These smart pointers use reference counting to keep track of each class instance and, when the last reference to a class instance disappears, automatically delete the instance.3 Smart pointers are generated by the Slice compiler for each class type. For a Slice class <classname>, the compiler generates a C++ smart pointer called <classname>Ptr. Rather than showing all the details of the generated class, here is the basic usage pattern: whenever you allocate a class instance on the heap, you simply assign the pointer returned from new to a smart pointer for the class. Thereafter, memory management is automatic and the class instance is deleted once the last smart pointer for it goes out of scope:
{                                       // Open scope
    TimeOfDayPtr tod = new TimeOfDayI;  // Allocate instance
    // Initialize...
    tod>hour = 18;
    tod>minute = 11;
    tod>second = 15;
    // ...
}                                       // No memory leak here!
As you can see, you use operator> to access the members of the class via its smart pointer. When the tod smart pointer goes out of scope, its destructor runs and, in turn, the destructor takes care of calling delete on the underlying class instance, so no memory is leaked.
The smart pointers perform reference counting of their underlying class instance:
• The constructor of a class sets its reference count to zero.
• Initializing a smart pointer with a dynamically-allocated class instance causes the smart pointer to increment the reference count for the class by one.
• Copy constructing a smart pointer increments the reference count for the class by one.
• Assigning one smart pointer to another increments the target’s reference count and decrements the source’s reference count. (Self-assignment is safe.)
• The destructor of a smart pointer decrements the reference count by one and calls delete on its class instance if the reference count drops to zero.
Figure 6.2 shows the situation after default-constructing a smart pointer as follows:
TimeOfDayPtr tod;
This creates a smart pointer with an internal null pointer.
Figure 6.2. Newly initialized smart pointer.
Constructing a class instance creates that instance with a reference count of zero; the assignment to the class pointer causes the smart pointer to increment the class’s reference count:
tod = new TimeOfDayI;   // Refcount == 1
The resulting situation is shown in Figure 6.3.
Figure 6.3. Initialized smart pointer.
Assigning or copy-constructing a smart pointer assigns and copy-constructs the smart pointer (not the underlying instance) and increments the reference count of the instance:
TimeOfDayPtr tod2(tod); // Copyconstruct tod2
TimeOfDayPtr tod3;
tod3 = tod;             // Assign to tod3
The situation after executing these statements is shown in Figure 6.4:
Figure 6.4. Three smart pointers pointing at the same class instance.
Continuing the example, we can construct a second class instance and assign it to one of the original smart pointers, tod2:
tod2 = new TimeOfDayI;
This decrements the reference count of the class originally denoted by tod2 and increments the reference count of the class that is assigned to tod2. The resulting situation is shown in Figure 6.5.
Figure 6.5. Three smart pointers and two instances.
You can clear a smart pointer by assigning zero to it:
tod = 0;        // Clear handle
As you would expect, this decrements the reference count of the instance, as shown in Figure 6.6.
Figure 6.6. Decremented reference count after clearing a smart pointer.
If a smart pointer goes out of scope, is cleared, or has a new instance assigned to it, the smart pointer decrements the reference count of its instance; if the reference count drops to zero, the smart pointer calls delete to deallocate the instance. The following code snippet deallocates the instance on the right by assigning tod2 to tod3:
tod3 = tod2;
This results in the situation shown in Figure 6.7.
Figure 6.7. Deallocation of an instance with a reference count of zero.

Copying and Assignment of Classes

Classes have a default memberwise copy constructor and assignment operator, so you can copy and assign class instances:
TimeOfDayPtr tod = new TimeOfDayI(2, 3, 4); // Create instance
TimeOfDayPtr tod2 = new TimeOfDayI(*tod);   // Copy instance

TimeOfDayPtr tod3 = new TimeOfDayI;
*tod3 = *tod;                               // Assign instance
Copying and assignment in this manner performs a memberwise shallow copy or assignment, that is, the source members are copied into the target members; if a class contains class members (which are mapped as smart pointers), what is copied or assigned is the smart pointer, not the target of the smart pointer.
To illustrate this, consider the following Slice definitions:
class Node {
    int i;
    Node next;
};
Assume that we initialize two instances of type Node as follows:
NodePtr p1 = new Node(99, new Node(48, 0));
NodePtr p2 = new Node(23, 0);

// ...

*p2 = *p1; // Assignment
After executing the first two statements, we have the situation shown in Figure 6.8.
Figure 6.8. Class instances prior to assignment.
After executing the assignment statement, we end up with the result shown in Figure 6.9.
Figure 6.9. Class instances after assignment.
Note that copying and assignment also works for the implementation of abstract classes, such as our TimeOfDayI class, for example:
class TimeOfDayI;

typedef IceUtil::Handle<TimeOfDayI> TimeOfDayIPtr;

class TimeOfDayI : virtual public TimeOfDay {
    // As before...
};
The default copy constructor and assignment operator will perform a memberwise copy or assignment of your implementation class:
TimeOfDayIPtr tod1 = new TimeOfDayI;
TimeOfDayIPtr tod2 = new TimeOfDayI(*tod1);     // Make copy
Of course, if your implementation class contains raw pointers (for which a memberwise copy would almost certainly be inappropriate), you must implement your own copy constructor and assignment operator that take the appropriate action (and probably call the base copy constructor or assignment operator to take care of the base part).
Note that the preceding code uses TimeOfDayIPtr as a typedef for IceUtil::Handle<TimeOfDayI>. This class is a template that contains the smart pointer implementation. If you want smart pointers for the implementation of an abstract class, you must define a smart pointer type as illustrated by this type definition.
Copying and assignment of classes also works correctly for derived classes: you can assign a derived class to a base class, but not vice-versa; during such an assignment, the derived part of the source class is sliced, as per the usual C++ assignment semantics.

Polymorphic Copying of Classes

As shown in Section 6.14.1 on page 237, the C++ mapping generates an ice_clone member function for every class:
class TimeOfDay : virtual public Ice::Object {
public:
    // ...

    virtual Ice::ObjectPtr ice_clone() const;
};
This member function makes a polymorphic shallow copy of a class: members that are not class members are deep copied; all members that are class members are shallow copied. To illustrate, consider the following class definition:
class Node {
    Node n1;
    Node n2;
};
Assume that we have an instance of this class, with the n1 and n2 members initialized to point at separate instances, as shown in Figure 6.10.
Figure 6.10. A class instance pointing at two other instances.
If we call ice_clone on the instance on the left, we end up with the situation shown in Figure 6.11.
Figure 6.11. Resulting graph after calling ice_clone on the left-most instance of Figure 6.10.
As you can see, class members are shallow copied, that is, ice_clone makes a copy of the class instance on which it is invoked, but does not copy any class instances that are pointed at by the copied instance.
Note that ice_clone returns a value of type Ice::ObjectPtr, to avoid problems with compilers that do not support covariant return types. The generated Ptr classes contain a dynamicCast member that allows you to safely down-cast the return value of ice_clone. For example, the code to achieve the situation shown in Figure 6.11 looks as follows:
NodePtr p1 = new Node(new Node, new Node);
NodePtr p2 = NodePtr::dynamicCast(p1>ice_clone());
ice_clone is generated by the Slice compiler for concrete classes (that is, classes that do not have operations). However, because classes with operations are abstract, for abstract classes, the generated ice_clone cannot know how to instantiate an instance of the derived concrete class (because the name of the derived class is not known). This means that, for abstract classes, the generated ice_clone throws a CloneNotImplementedException.
If you want to clone the implementation of an abstract class, you must override the virtual ice_clone member in your concrete implementation class. For example:
class TimeOfDayI : public TimeOfDay {
public:
    virtual Ice::ObjectPtr ice_clone() const
    {
        return new TimeOfDayI(*this);
    }
};

Null Smart Pointers

A null smart pointer contains a null C++ pointer to its underlying instance. This means that if you attempt to dereference a null smart pointer, you get an IceUtil::NullHandleException:
TimeOfDayPtr tod;               // Construct null handle

try {
    tod>minute = 0;            // Dereference null handle
    assert(false);              // Cannot get here
} catch (const IceUtil::NullHandleException&) {
    ; // OK, expected
} catch (...) {
    assert(false);              // Must get NullHandleException
}

Preventing Stack-Allocation of Class Instances

The Ice C++ mapping expects class instances to be allocated on the heap. Allocating class instances on the stack or in static variables is pragmatically useless because all the Ice APIs expect parameters that are smart pointers, not class instances. This means that, to do anything with a stack-allocated class instance, you must initialize a smart pointer for the instance. However, doing so does not work because it inevitably leads to a crash:
{                               // Enter scope
    TimeOfDayI t;               // Stackallocated class instance
    TimeOfDayPtr todp;          // Handle for a TimeOfDay instance

    todp = &t;                  // Legal, but dangerous
    // ...
}                               // Leave scope, looming crash!
This goes badly wrong because, when todp goes out of scope, it decrements the reference count of the class to zero, which then calls delete on itself. However, the instance is stack-allocated and cannot be deleted, and we end up with undefined behavior (typically, a core dump).
The following attempt to fix this is also doomed to failure:
{                               // Enter scope
    TimeOfDayI t;               // Stackallocated class instance
    TimeOfDayPtr todp;          // Handle for a TimeOfDay instance

    todp = &t;                  // Legal, but dangerous
    // ...
    todp = 0;                   // Crash imminent!
}
This code attempts to circumvent the problem by clearing the smart pointer explicitly. However, doing so also causes the smart pointer to drop the reference count on the class to zero, so this code ends up with the same call to delete on the stack-allocated instance as the previous example.
The upshot of all this is: never allocate a class instance on the stack or in a static variable. The C++ mapping assumes that all class instances are allocated on the heap and no amount of coding trickery will change this.4
You can prevent allocation of class instances on the stack or in static variables by adding a protected destructor to your implementation of the class: if a class has a protected destructor, allocation must be made with new, and static or stack allocation causes a compile time error. In addition, explicit calls to delete on a heap-allocated instance also are flagged at compile time. You may want to habitually add a protected destructor to your implementation of abstract Slice classes to protect yourself from accidental heap allocation, as shown on page 241. (For Slice classes that do not have operations, the Slice compiler automatically adds a protected destructor.)

Smart Pointers and Exception Safety

Smart pointers are exception safe: if an exception causes the thread of execution to leave a scope containing a stack-allocated smart pointer, the C++ run time ensures that the smart pointer’s destructor is called, so no resource leaks can occur:
{ // Enter scope...

    TimeOfDayPtr tod = new TimeOfDayI; // Allocate instance

    someFuncThatMightThrow();          // Might throw...

    // ...

} // No leak here, even if an exception is thrown
If an exception is thrown, the destructor of tod runs and ensures that it deallocates the underlying class instance.
There is one potential pitfall you must be aware of though: if a constructor of a base class throws an exception, and another class instance holds a smart pointer to the instance being constructed, you can end up with a double deallocation. Consider the following example, which reduces the problem to its bare-bones minimum:
class Base;
typedef IceUtil::Handle<Base> BasePtr;

class Listener : public virtual IceUtil::Shared {
public:
    Listener(BasePtr p) {
        _parent = p;
    }

    virtual ~Listener() {
        _parent = 0;
    }

private:
    BasePtr _parent;
};

typedef IceUtil::Handle<Listener> ListenerPtr;

class Base : public virtual IceUtil::Shared {
public:
    Base() {
        _listener = new Listener(this);
    }

    virtual ~Base() {
        _listener = 0;
    }

private:
    ListenerPtr _listener;
};

class Derived : public virtual Base {
public:
    Derived() {
        if (errorCondition)
            throw "Some error";
    }
};

typedef IceUtil::Handle<Derived> DerivedPtr;

int main()
{
    try {
        DerivedPtr d = new Derived;
    } catch (...) {
        // ...
    }
    return 0;
}
This type of code is used, for example, for the listener pattern [2]. Consider what happens when the statement
DerivedPtr d = new Derived;
is executed in main and errorCondition is true:
1. The Base constructor is called (because Base is a base class of Derived).
2. The constructor of Base allocates an instance of Listener, which results in a call to the Listener constructor.
3. The Listener constructor assigns its argument to the _parent member, which leaves the reference count of the Derived object being instantiated at one.
4. The Listener constructor completes and returns control to the Base constructor, which assigns the newly-constructed Listener object to its _listener member, of type ListenerPtr. At this point, the reference count of the Listener object changes from zero to one.
5. The Base constructor completes, so the C++ run time continues to construct the Derived object being instantiated by calling the Derived constructor.
6. The Derived constructor throws an exception. As a result, the C++ run time calls the Base destructor.
7. The Base destructor assigns null to its _listener member, which drops the reference count of its Listener object to zero. As a result, the Listener object calls delete this;.
8. The Listener destructor runs and assigns null to its _listener member. The _listener smart pointer drops the reference count of its Base object to zero. As a result, the Base object calls delete this;.
9. The C++ run time calls the Base destructor.
At this point, the program shows undefined behavior (usually, dumps core) because the Base destructor is called a second time on the same object.
We can solve the problem by telling the Derived instance that it should not deallocate itself while the Derived constructor may still throw an exception. The IceUtil::Shared class provides a __setNoDelete member function that allows us to do this:
class Derived : public virtual Base {
public:
    Derived() {
        __setNoDelete(true);
        if (errorCondition)
            throw "Some error";
        __setNoDelete(false);
    }
};
Here, we have changed the Derived constructor to disable deallocation of its object until we know that no more exceptions can be thrown. Once the constructor is certain to complete successfully, it enables deallocation again, so the object will be deallocated once its reference count drops to zero.

Smart Pointers and Cycles

One thing you need to be aware of is the inability of reference counting to deal with cyclic dependencies. For example, consider the following simple self-referential class:
class Node {
    int val;
    Node next;
};
Intuitively, this class implements a linked list of nodes. As long as there are no cycles in the list of nodes, everything is fine, and our smart pointers will correctly deallocate the class instances. However, if we introduce a cycle, we have a problem:
{                          // Open scope...

    NodePtr n1 = new Node; // N1 refcount == 1
    NodePtr n2 = new Node; // N2 refcount == 1
    n1>next = n2;         // N2 refcount == 2
    n2>next = n1;         // N1 refcount == 2

} // Destructors run:      // N2 refcount == 1,
                           // N1 refcount == 1, memory leak!
The nodes pointed to by n1 and n2 do not have names but, for the sake of illustration, let us assume that n1’s node is called N1, and n2’s node is called N2. When we allocate the N1 instance and assign it to n1, the smart pointer n1 increments N1’s reference count to 1. Similarly, N2’s reference count is 1 after allocating the node and assigning it to n2. The next two statements set up a cyclic dependency between n1 and n2 by making their next pointers point at each other. This sets the reference count of both N1 and N2 to 2. When the enclosing scope closes, the destructor of n2 is called first and decrements N2’s reference count to 1, followed by the destructor of n1, which decrements N1’s reference count to 1. The net effect is that neither reference count ever drops to zero, so both N1 and N2 are leaked.

Garbage Collection of Class Instances

The previous example illustrates a problem that is generic to using reference counts for deallocation: if a cyclic dependency exists anywhere in a graph (possibly via many intermediate nodes), all nodes in the cycle are leaked.
To avoid memory leaks due to such cycles, Ice for C++ contains a garbage collector. The collector identifies class instances that are part of one or more cycles but are no longer reachable from the program and deletes such instances:
• By default, garbage is collected whenever you destroy a communicator. This means that no memory is leaked when your program exits. (Of course, this assumes that you correctly destroy your communicators as described in Section 8.3.)
• You can also explicitly call the garbage collector by calling Ice::collectGarbage. For example, the leak caused by the preceding example can be avoided as follows:
{                          // Open scope...

    NodePtr n1 = new Node; // N1 refcount == 1
    NodePtr n2 = new Node; // N2 refcount == 1
    n1>next = n2;         // N1 refcount == 2
    n2>next = n1;         // N2 refcount == 2

} // Destructors run:      // N2 refcount == 1,
                           // N1 refcount == 1

Ice::collectGarbage();     // Deletes N1 and N2
The call to Ice::collectGarbage deletes the no longer reachable instances N1 and N2 (as well as any other non-reachable instances that may have accumulated earlier).
• Deleting leaked memory with explicit calls to the garbage collector can be inconvenient because it requires polluting the code with calls to the collector. You can ask the Ice run time to run a garbage collection thread that periodically cleans up leaked memory by setting the property Ice.GC.Interval to a non-zero value.5 For example, setting Ice.GC.Interval to 5 causes the collector thread to run the garbage collector once every five seconds. You can trace the execution of the collector by setting Ice.Trace.GC to a non-zero value (Appendix C).
Note that the garbage collector is useful only if your program actually creates cyclic class graphs. There is no point in running the garbage collector in programs that do not create such cycles. (For this reason, the collector thread is disabled by default and runs only if you explicitly set Ice.GC.Interval to a non-zero value.)

Smart Pointer Comparison

As for proxy handles (see Section 6.11.4 on page 224), class handles support the comparison operators ==, !=, and <. This allows you to use class handles in STL sorted containers. Be aware that, for smart pointers, object identity is not used for the comparison, because class instances do not have identity. Instead, these operators simply compare the memory address of the classes they point to. This means that operator== returns true only if two smart pointers point at the same physical class instance:
// Create a class instance and initialize
//
TimeOfDayIPtr p1 = new TimeOfDayI;
p1>hour = 23;
p1>minute = 10;
p1>second = 18;

// Create another class instance with
// the same member values
//
TimeOfDayIPtr p2 = new TimeOfDayI;
p2>hour = 23;
p2>minute = 10;
p2>second = 18;

assert(p1 != p2);       // The two do not compare equal

TimeOfDayIPtr p3 = p1;  // Point at first class again

assert(p1 == p3);       // Now they compare equal

1
The ProxyType and PointerType definitions are for template programming (see page 219).

2
We discuss the motivation for the protected destructor on page 252.

3
Smart pointer classes are an example of the RAII (Resource Acquisition Is Initialization) idiom [20].

4
You could abuse the __setNoDelete member (described on page 256) to disable deallocation, but we strongly discourage you from doing this.

5
See Chapter 30 for how to set properties.

Table of Contents Previous Next
Logo