Object Containment and Delegation
- 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.
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.
select bitarraycol from bitarraytab;
- 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();
- 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();
- 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;
- Define a pointer to hold an intermediate integer value object.
ITValue *int_value;
- Make the ITEssential interface of Bitarray as the
outer controlling unknown pointer.
desc.vf_outerunknown = this;
- 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.
- Copy the ITEssential interface into a class member.
int_essential = desc.vf_outerunknown;
The object constructor overwrites the ITEssential instance named int_essential.
- When the object is deleted, release the interface of the integer
subobject.
int_essential->Release();
- 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); } }
- 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.