Object Containment and Delegation

Objects that contain other objects are called container objects. There are two fundamental types of container objects:
Base type containers
Value objects that contain C++ base type instances (and do not contain other objects). For an example of base type containers, see Value objects and connection events.
Object containers
Value objects that contain other value objects. Object containers are created by using a technique called object delegation, in which the container object uses a predefined constituent object to define its subobjects.

Object delegation allows objects to be reused, as in C++ object inheritance, but protects against base-class fragility-the tendency for base classes to evolve beneath derived classes. Instead of deriving one class from another, the capabilities of one object are combined with the capabilities of another, through a process called interface delegation.

In interface delegation, a parent object exposes the interfaces of a contained object as if they were its own. The contained object is supplied with the controlling ITEssential pointer (in COM, a controlling unknown pointer) when it is constructed; this controlling ITEssential is the ITEssential interface of the parent object.

When any of the ITEssential methods of the delegated interface of the subobject are called (for example, QueryInterface, AddRef, and Release), they are delegated to the controlling ITEssential interface. For example, if the QueryInterface method of a delegated interface is called, the QueryInterface method of the parent object is called. Reference counting is also performed on the parent object rather than the subobject.

To ensure that the parent can extract interfaces from the subobject and delete it, the parent object must have a pointer to one interface that is not delegated. This interface is the ITEssential interface of the subobject, which must never be exposed outside of the parent object.

The following figure illustrates object delegation.
Figure 1. Object delegation
begin figure description - This graphic is composed of rectangles and arrows. The graphic is a flow-graph leads back into itself. The ITValue object has an arrow that leads into a rectangle marked Noncontrol object (integer object) which has an arrow marked Pointer to Controlling ITEssential which leads into the ITEssential object. An arrow leads from the controlling ITEssential object into a rectangle labeled Parent object (Bitarray object). Another arrow leads into the Parent object (Bitarray object) rectangle from the ITContainCvt object. An arrow labeled Pointer to noncontrol object leads from the "Parent object (Bitarray object)" rectangle into the noncontrol ITEssential object. An arrow connects the noncontrol ITEssential object with the Noncontrol object (Integer object) rectangle, which connects the ITValue object and the controlling ITEssential object. - end figure description
Object delegation is demonstrated by the delegate.cpp example, which is in turn driven by the deldrv.cpp example file. This example requires a bit array server data type and table defined by the following SQL statements:
create distinct type bitarray as integer;
create table bitarraytab (bitarraycol bitarray);
insert into bitarraytab values ('1');

The bit array value object implemented in the delegate.cpp example is created by aggregating the integer value object. Of the interfaces exposed by this subobject, only a few methods of the ITContainCvt interface of the container object and the ITValue interface of the integer value object are exposed outside of the bit array object. The interface of the integer value object is exposed through delegation.

A bit array is retrieved by the following query, which is issued in the deldrv.cpp example file:
select bitarraycol from bitarraytab;
The following excerpts from the delegate.cpp example show how to use object delegation to delegate the responsibility for creating objects to an ITValue-interface-exposing subobject within the Bitarray class:
  1. Define the various ITEssential methods.
    class Bitarray : public ITContainCvt
    {
    public:
        // Overrides of ITEssential methods
        virtual ITOpErrorCode IT_STDCALL QueryInterface(const ITInterfaceID &ifiid,
                                                        void **resultif);
        virtual unsigned long IT_STDCALL AddRef();
        virtual unsigned long IT_STDCALL Release();
  2. Define the ITContainCvt methods. Because not all of the methods of the ITContainCvt interface of the nested object are used, the parent object cannot delegate the ITContainCvt interface to the subobject, as it does for the ITValue interface.
    // Overrides of ITContainCvt methods
    virtual long IT_STDCALL NumItems();
  3. Define a pointer for the ITEssential interface of the subobject. The object must retain the ITEssential interface of the integer object, so it can release the subobject when the parent object is deleted. This interface is never passed back outside of a Bitarray object.
    ITEssential *int_essential;
  4. Define a pointer to hold an intermediate integer value object.
    ITValue *int_value;
  5. Make the ITEssential interface of Bitarray as the outer controlling unknown pointer.
    desc.vf_outerunknown = this;
  6. To create an integer subobject for delegation, the Bitarray constructor uses a local instance of ITMVDesc. This instance is identical to the ITMVDesc instance of Bitarray, except for the use of the integer ITTypeInfo that the Bitarray constructor retrieves by using ITTypeInfo::Source().
    ITMVDesc desc = *mv;
    desc.vf_origtypeinfo = (ITTypeInfo *)mv->vf_origtypeinfo->Source();

    The ITMVDesc instance is passed to ITFactoryList::DatumToValue() to instantiate the integer object and return a pointer to its ITValue. Bitarray retains this pointer for delegation.

  7. Copy the ITEssential interface into a class member.
    int_essential = desc.vf_outerunknown;

    The object constructor overwrites the ITEssential instance named int_essential.

  8. When the object is deleted, release the interface of the integer subobject.
    int_essential->Release();
  9. If the application requests an interface that is not supported by this object, ask the integer subobject if it supports the interface.
    ITOpErrorCode
    Bitarray::QueryInterface(const ITInterfaceID &iid, 
                  void **ifptr)
    {
        switch (ITIIDtoSID(iid))
            {
        case ITEssentialSID: case ITContainCvtSID:
            *ifptr = this;
            AddRef();
            return IT_QUERYINTERFACE_SUCCESS;
    
        default:
            // This object does not support the interface. Try the
            // delegated subobject...if the subobject supports the
            // interface, it will increment the reference counter on the
            // controlling unknown, so we don't need to increment it
            // here (except if you ask the subobject for its ITEssential
            // interface, in which case it will increment its own
            // reference count).
            return int_essential->QueryInterface(iid, ifptr);
            }
    }
  10. Implement the ITContainCvt methods.
    // ContainCvt implementation
    ITBool                   
    Bitarray::ConvertTo(long item, int &dbvalue)
    {
        if (int_value->IsNull() || item >= NumItems())
            return FALSE;
        const char *valasstr = int_value->Printable();
        int val = atoi(valasstr);
        dbvalue = !!(val & (1 << (NBITS - 1 - item)));
        return TRUE;
    } 
    
    ITBool
    Bitarray::ConvertFrom(long item, int val)
    {
        if(NumItems() <= item)
            return FALSE;
        int value = val ? value | (1 << (NBITS - 1 - item))
            : value & ~(1 << (NBITS - 1 - item));
        char valasstr[32];
        sprintf(valasstr, "%d", value);
        return int_value->FromPrintable(valasstr);
    }
    
    long
    Bitarray::NumItems()
    {
      return NBITS;
    }

Because of the way the ITValue interface is delegated, this forwarding is not necessary for the ITValue interface methods.