[ Pobierz całość w formacie PDF ] .Efficiency dictates that a subclass reach into its superclass componentsdirectly.Information hiding and maintainability require that a superclass hide itsown representation as best as possible from its subclasses.If we opt for the latter,we should provide access functions for all those components of a superclass whicha subclass is allowed to look at, and modification functions for those components, ifany, which the subclass may modify.38 4 Inheritance Code Reuse and Refinement___________________________________________________________________________Access and modification functions are statically linked methods.If we declarethem in the representation file for the superclass, which is only included in theimplementations of subclasses, we can use macros, because side effects are noproblem if a macro uses each argument only once.As an example, in Point.r wedefine the following access macros:*#define x(p) (((const struct Point *)(p)) > x)#define y(p) (((const struct Point *)(p)) > y)These macros can be applied to a pointer to any object that starts with a structPoint, i.e., to objects from any subclass of our points.The technique is to up-castthe pointer into our superclass and reference the interesting component there.const in the cast blocks assignments to the result.If const were omitted#define x(p) (((struct Point *)(p)) > x)a macro call x(p) produces an l-value which can be the target of an assignment.Abetter modification function would be the macro definition#define set_x(p,v) (((struct Point *)(p)) > x = (v))which produces an assignment.Outside the implementation of a subclass we can only use statically linkedmethods for access and modification functions.We cannot resort to macrosbecause the internal representation of the superclass is not available for the macrosto reference.Information hiding is accomplished by not providing the representa-tion file Point.r for inclusion into an application.The macro definitions demonstrate, however, that as soon as the representa-tion of a class is available, information hiding can be quite easily defeated.Here is away to conceal struct Point much better.Inside the superclass implementation weuse the normal definition:struct Point {const void * class;int x, y; /* coordinates */};For subclass implementations we provide the following opaque version:struct Point {const char _ [ sizeof( struct {const void * class;int x, y; /* coordinates */})];};This structure has the same size as before, but we can neither read nor write thecomponents because they are hidden in an anonymous interior structure.Thecatch is that both declarations must contain identical component declarations andthis is difficult to maintain without a preprocessor.____________________________________________________________________________________________* In ANSI-C, a parametrized macro is only expanded if the macro name appears before a left parenthesis.Elsewhere, the macro name behaves like any other identifier.4.7 Subclass Implementation Circle 39___________________________________________________________________________4.7 Subclass Implementation CircleWe are ready to write the complete implementation of circles, where we canchoose whatever techniques of the previous sections we like best.Object-orientation prescribes that we need a constructor, possibly a destructor,Circle_draw(), and a type description Circle to tie it all together.In order to exer-cise our methods, we include Circle.h and add the following lines to the switch inthe test program in section 4.1:case c :p = new(Circle, 1, 2, 3);break;Now we can observe the following behavior of the test program:$ circles p c"." at 1,2"." at 11,22circle at 1,2 rad 3circle at 11,22 rad 3The circle constructor receives three arguments: first the coordinates of thecircle s point and then the radius.Initializing the point part is the job of the pointconstructor.It consumes part of the argument list of new().The circle constructoris left with the remaining argument list from which it initializes the radius.A subclass constructor should first let the superclass constructor do that part ofthe initialization which turns plain memory into the superclass object.Once thesuperclass constructor is done, the subclass constructor completes initialization andturns the superclass object into a subclass object.For circles this means that we need to call Point_ctor().Like all dynamicallylinked methods, this function is declared static and thus hidden inside Point.c.However, we can still get to the function by means of the type descriptor Pointwhich is available in Circle.c:static void * Circle_ctor (void * _self, va_list * app){ struct Circle * self =((const struct Class *) Point) > ctor(_self, app);self > rad = va_arg(* app, int);return self;}It should now be clear why we pass the address app of the argument list pointer toeach constructor and not the va_list value itself: new() calls the subclass construc-tor, which calls its superclass constructor, and so on.The supermost constructor isthe first one to actually do something, and it gets first pick at the left end of theargument list passed to new().The remaining arguments are available to the nextsubclass and so on until the last, rightmost arguments are consumed by the finalsubclass, i.e., by the constructor directly called by new().Destruction is best arranged in the exact opposite order: delete() calls the sub-class destructor.It should destroy its own resources and then call its direct super-class destructor which can destroy the next set of resources and so on.Construc-40 4 Inheritance Code Reuse and Refinement___________________________________________________________________________tion happens superclass before subclass, destruction happens in reverse, subclassbefore superclass, circle part before point part.Here, however, nothing needs tobe done.We have worked on Circle_draw() before.We use visible components andcode the representation file Point
[ Pobierz całość w formacie PDF ]
zanotowane.pldoc.pisz.plpdf.pisz.plhanula1950.keep.pl
|