Difference between revisions of "Overriding Constructors"

From MorphOS Library

(Added a note about Window object having one child.)
m (Linked to Polish version.)
 
(4 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
''Grzegorz Kraszewski''
 
''Grzegorz Kraszewski''
 +
----
 +
<small>This article in other languages: [[Przeciążanie konstruktorów|Polish]]
  
  
 
==Objects without child objects==
 
==Objects without child objects==
  
An object constructor (''OM_NEW()'' method), takes the same message structure ''opSet'' as ''[[Overriding OM_SET()|OM_SET()]]'' method. The message contains ''ops_AttrList'' field, being a pointer to a [[Taglists|taglist]] containing initial object's attributes. Implementation of a constructor for an object not chaving child objects is simple. The superclass constructor is called first, then, if it succeeds, the constuructor initializes object instance data, allocates resources needed and sets initial values of attributes from tags passed via ''ops_AttrList''.
+
An object constructor (''OM_NEW()'' method), takes the same message structure ''opSet'' as the ''[[Overriding OM_SET()|OM_SET()]]'' method. The message contains the ''ops_AttrList'' field, being a pointer to a [[Taglists|taglist]] containing the initial object's attributes. Implementation of a constructor for an object without child objects is simple. The superclass constructor is called first, then, if it succeeds, the constructor initializes object instance data, allocates resources needed and sets initial values of attributes from tags passed via ''ops_AttrList''.
  
A rule of thumb when overriding constructor is to '''never leave a half-constructed object'''. The counstructor should either return a fully constructed object, or fail completely, freeing all succesfully obtained resources. It is important if the object obtains more than one resource and any of resource allocation may fail (for example allocating a big chunk of memory or opening a file). An example implementation below obtains three resources: ''A'', ''B'' and ''C'':
+
A rule of thumb when overriding constructors is to '''never leave a half-constructed object'''. The constructor should either return a fully constructed object, or fail completely, freeing all successfully obtained resources. This is important if the object obtains more than one resource and any of the resource allocation has failed (for example allocating a big chunk of memory or opening a file). An example implementation below obtains three resources: ''A'', ''B'' and ''C'':
  
 
  IPTR MyClassNew(Class *cl, Object *obj, struct opSet *msg)
 
  IPTR MyClassNew(Class *cl, Object *obj, struct opSet *msg)
Line 25: Line 27:
 
  }
 
  }
  
If the object destructor frees resources  ''A'', ''B'' and ''C'' (which would be logical considering the constructor allocates them), the cleanup job may be delegated to the destructor. It requires however, that the destructor must be prepared for destruction of not fully constructed object. It can't assume all three resources have been allocated, so it should check every resource pointer against ''NULL'' before calling a freeing function. The desctructor also takes care of calling a superclass destructor when resources are freed. See [[Overriding Destructors]] for a destructor example code and explanation.
+
If the object destructor frees resources  ''A'', ''B'' and ''C'' (which would be logical considering the constructor allocates them), the cleanup job may be delegated to the destructor. It requires however, that the destructor must be prepared for destruction of a not fully constructed object. It can't assume all three resources have been allocated, so it should check every resource pointer against ''NULL'' before calling a freeing function. The destructor also takes care of calling a superclass destructor when resources are freed. See [[Overriding Destructors]] for some destructor example code and explanation.
  
The only question remaining is what ''CoerceMethod()'' does and why it is used instead of a plain ''DoMethod()''? The ''CoerceMethod()'' call works exactly the same as ''DoMethod()'', but performs '''method coercion''' by forced call to the dispatcher of the class specified as the first argument instead of the dispatcher of the object's true class. It makes a difference, when the class in question is later subclassed. The flowchart below explains the problem:
+
The only question remaining is what ''CoerceMethod()'' does and why it is used instead of a plain ''DoMethod()''? The ''CoerceMethod()'' call works exactly the same as ''DoMethod()'', but performs '''method coercion''' by a forced call to the dispatcher of the class specified as the first argument instead of the dispatcher of the object's true class. It makes a difference, when the class in question is later subclassed. The flowchart below explains the problem:
  
  
Line 33: Line 35:
  
  
The class ''B'' on the diargam is a subclass of the class ''A'' and similarly, the class ''C'' is a subclass of ''B''. Let's assume an object of the class ''C'' is being constructed. As every constructor calls the superclass first, the call goes up to ''rootclass'' (the root of all BOOPSI classes) first. Then going down the class tree, every class constructor allocates its resources. Unfortunately the constructor of class ''A'' has been unable to allocate one of resources and decided to fail. If it would just call ''DoMethod(obj, OM_DISPOSE)'' it will unneccesarily execute destructors in classes ''B'' and ''C'', while constructors in these classes have been not yet fully executed. Even if these destructors can cope with this, calling them is superfluous. With ''CoerceMethod()'' destructor in the class ''A'' is called directly. Then class ''A'' constructor returns NULL, which makes constructors in classes ''B'' and ''C'' to fail immediately wihout resource allocation attempts.
+
The class ''B'' on the diagram is a subclass of the class ''A'' and similarly, the class ''C'' is a subclass of ''B''. Let's assume an object of the class ''C'' is being constructed. As every constructor calls the superclass first, the call goes up to ''rootclass'' (the root of all BOOPSI classes) first. Then going down the class tree, every class constructor allocates its resources. Unfortunately the constructor of class ''A'' has been unable to allocate one of its resources and decided to fail. If it had just called ''DoMethod(obj, OM_DISPOSE)'' it will unnecessarily execute destructors in classes ''B'' and ''C'', while constructors in these classes have been not yet fully executed. Even if these destructors can cope with this, calling them is superfluous. With the ''CoerceMethod()'' the destructor in the class ''A'' is called directly. Then class ''A'' constructor returns NULL, which causes constructors in classes ''B'' and ''C'' to fail immediately without resource allocation attempts.
  
  
 
==Objects with child objects==
 
==Objects with child objects==
  
While retaining the same principles, a constructor of object having subobjects is designed a bit differently. The most commonly subclassed classes able to have child objects are ''Application'', and ''Group''. The ''Window'' class is also often subclassed similar way. While a ''Window'' object can have only one child, specified by ''MUIA_Window_RootObject'', this child has multiple subobjects usually. The constructor should create its child objects first, then insert them into the ''ops_AttrList'' [[Taglists|taglist]] and call the superclass constructor. If it succeeds, then resources may be allocated if needed. As any of the three constructor stages may fail, proper handling of errors becomes complicated. Also inserting objects created into the taglist as values of child tags (like ''MUIA_Group_Child'') is cumbersome. Fortunately one can use the ''DoSuperNew()'' function, which merges subobjects creation and calling the superclass into one operation. It also provides automatic handling of failed child object construction. An example below is a constructor for a ''Group'' subclass putting two ''Text'' objects in the group.
+
While retaining the same principles, the constructor of an object with subobjects is designed a bit differently. The most commonly subclassed classes able to have child objects are ''Application'', and ''Group''. The ''Window'' class is also often subclassed in a similar way. While a ''Window'' object can have only one child, specified by ''MUIA_Window_RootObject'', this child often has multiple subobjects. The constructor should create its child objects first, then insert them into the ''ops_AttrList'' [[Taglists|taglist]] and call the superclass constructor. If it succeeds, then resources may be allocated if needed. As any of the three constructor stages may fail, proper handling of errors becomes complicated. Also inserting objects created into the taglist as values of child tags (like ''MUIA_Group_Child'') is cumbersome. Fortunately one can use the ''DoSuperNew()'' function, which merges the creation of subobjects and the calling of the superclass into one operation. It also provides automatic handling of failed child object construction. An example below is a constructor for a ''Group'' subclass putting two ''Text'' objects in the group.
  
 
  IPTR MyClassNew(Class *cl, Object *obj, struct opSet *msg)
 
  IPTR MyClassNew(Class *cl, Object *obj, struct opSet *msg)
Line 64: Line 66:
 
  }
 
  }
  
An important thing to observe is the fact, that ''DoSuperNew()'' '''merges''' the taglist passed to the constructor via the message ''ops_AttrList'' field and the one specified in the function arguments list. It is done with a special ''TAG_MORE'' tag, which direct a taglist iterator (like ''NextTagItem()'' function) to jump to another taglist pointed by the value of this tag. Taglist merging allows for modifying the object being constructed with tags passed to ''NewObject()'', for example adding a frame or background to the group in the above example.
+
An important thing to observe is the fact, that ''DoSuperNew()'' '''merges''' the taglist passed to the constructor via the message ''ops_AttrList'' field and the one specified in the function arguments list. It is done with a special ''TAG_MORE'' tag, which directs a taglist iterator (like ''NextTagItem()'' function) to jump to another taglist pointed by the value of this tag. Taglist merging allows for modifying the object being constructed with tags passed to ''NewObject()'', for example adding a frame or background to the group in the above example.
  
The automatic handling of failed child objects works in the following way: when a subobject fails, its constructor returns ''NULL''. This ''NULL'' value is then inserted as a value of a "child" tag (''MUIA_Group_Child'') in the example. All MUI classes able to have child objects are designed in a way that:
+
The automatic handling of failed child objects works in the following way: when a subobject fails, its constructor returns ''NULL''. This ''NULL'' value is then inserted as the value of a "child" tag (''MUIA_Group_Child'') in the example. All MUI classes able to have child objects are designed in a way that:
* the constructor fails if any "child" tag has ''NULL'' value,
+
* the constructor fails if any "child" tag has a ''NULL'' value,
* the constructor disposes any succesfully constructed child objects before exiting.
+
* the constructor disposes any successfully constructed child objects before exiting.
 
Finally ''DoSuperNew()'' returns ''NULL'' as well. This design ensures that in case of any fail while building the application, all objects created are disposed and there are no orphaned ones.
 
Finally ''DoSuperNew()'' returns ''NULL'' as well. This design ensures that in case of any fail while building the application, all objects created are disposed and there are no orphaned ones.

Latest revision as of 17:40, 24 January 2011

Grzegorz Kraszewski


This article in other languages: Polish


Objects without child objects

An object constructor (OM_NEW() method), takes the same message structure opSet as the OM_SET() method. The message contains the ops_AttrList field, being a pointer to a taglist containing the initial object's attributes. Implementation of a constructor for an object without child objects is simple. The superclass constructor is called first, then, if it succeeds, the constructor initializes object instance data, allocates resources needed and sets initial values of attributes from tags passed via ops_AttrList.

A rule of thumb when overriding constructors is to never leave a half-constructed object. The constructor should either return a fully constructed object, or fail completely, freeing all successfully obtained resources. This is important if the object obtains more than one resource and any of the resource allocation has failed (for example allocating a big chunk of memory or opening a file). An example implementation below obtains three resources: A, B and C:

IPTR MyClassNew(Class *cl, Object *obj, struct opSet *msg)
{  
  if (obj = DoSuperMethodA(cl, obj, (Msg)msg))
  {
    struct MyClassData *d = (struct MyClassData*)INST_DATA(cl, obj);

    if ((d->ResourceA = ObtainResourceA()
     && (d->ResourceB = ObtainResourceB()
     && (d->ResourceC = ObtainResourceC())
    {
      return (IPTR)obj;    /* success */
    }
    else CoerceMethod(cl, obj, OM_DISPOSE);
  }
  return NULL;
}

If the object destructor frees resources A, B and C (which would be logical considering the constructor allocates them), the cleanup job may be delegated to the destructor. It requires however, that the destructor must be prepared for destruction of a not fully constructed object. It can't assume all three resources have been allocated, so it should check every resource pointer against NULL before calling a freeing function. The destructor also takes care of calling a superclass destructor when resources are freed. See Overriding Destructors for some destructor example code and explanation.

The only question remaining is what CoerceMethod() does and why it is used instead of a plain DoMethod()? The CoerceMethod() call works exactly the same as DoMethod(), but performs method coercion by a forced call to the dispatcher of the class specified as the first argument instead of the dispatcher of the object's true class. It makes a difference, when the class in question is later subclassed. The flowchart below explains the problem:


Coercemethod.png


The class B on the diagram is a subclass of the class A and similarly, the class C is a subclass of B. Let's assume an object of the class C is being constructed. As every constructor calls the superclass first, the call goes up to rootclass (the root of all BOOPSI classes) first. Then going down the class tree, every class constructor allocates its resources. Unfortunately the constructor of class A has been unable to allocate one of its resources and decided to fail. If it had just called DoMethod(obj, OM_DISPOSE) it will unnecessarily execute destructors in classes B and C, while constructors in these classes have been not yet fully executed. Even if these destructors can cope with this, calling them is superfluous. With the CoerceMethod() the destructor in the class A is called directly. Then class A constructor returns NULL, which causes constructors in classes B and C to fail immediately without resource allocation attempts.


Objects with child objects

While retaining the same principles, the constructor of an object with subobjects is designed a bit differently. The most commonly subclassed classes able to have child objects are Application, and Group. The Window class is also often subclassed in a similar way. While a Window object can have only one child, specified by MUIA_Window_RootObject, this child often has multiple subobjects. The constructor should create its child objects first, then insert them into the ops_AttrList taglist and call the superclass constructor. If it succeeds, then resources may be allocated if needed. As any of the three constructor stages may fail, proper handling of errors becomes complicated. Also inserting objects created into the taglist as values of child tags (like MUIA_Group_Child) is cumbersome. Fortunately one can use the DoSuperNew() function, which merges the creation of subobjects and the calling of the superclass into one operation. It also provides automatic handling of failed child object construction. An example below is a constructor for a Group subclass putting two Text objects in the group.

IPTR MyClassNew(Class *cl, Object *obj, struct opSet *msg)
{  
  if (obj = DoSuperNew(cl, obj,
    MUIA_Group_Child, MUI_NewObject(MUIC_Text,
      /* attributes for the first subobject */
    TAG_END),
    MUIA_Group_Child, MUI_NewObject(MUIC_Text,
      /* attributes for the second subobject */
    TAG_END),
  TAG_MORE, msg->ops_AttrList)) 
  {
    struct MyClassData *d = (struct MyClassData*)INST_DATA(cl, obj);

    if ((d->ResourceA = ObtainResourceA()
     && (d->ResourceB = ObtainResourceB()
     && (d->ResourceC = ObtainResourceC())
    {
      return (IPTR)obj;    /* success */
    }
    else CoerceMethod(cl, obj, OM_DISPOSE);
  }
  return NULL;
}

An important thing to observe is the fact, that DoSuperNew() merges the taglist passed to the constructor via the message ops_AttrList field and the one specified in the function arguments list. It is done with a special TAG_MORE tag, which directs a taglist iterator (like NextTagItem() function) to jump to another taglist pointed by the value of this tag. Taglist merging allows for modifying the object being constructed with tags passed to NewObject(), for example adding a frame or background to the group in the above example.

The automatic handling of failed child objects works in the following way: when a subobject fails, its constructor returns NULL. This NULL value is then inserted as the value of a "child" tag (MUIA_Group_Child) in the example. All MUI classes able to have child objects are designed in a way that:

  • the constructor fails if any "child" tag has a NULL value,
  • the constructor disposes any successfully constructed child objects before exiting.

Finally DoSuperNew() returns NULL as well. This design ensures that in case of any fail while building the application, all objects created are disposed and there are no orphaned ones.