Return to ING home page2.3 Programming object classes in C

This page is part of the ING document INS-DAS-31 Design notes for udas_camera

Naming and packaging

An UltraDAS class is written as a small subroutine library acting on a particular type of structure. The methods of the class are provided as functions in one C source-file and the public interface is declared in a C header-file with a matching name.  For example, these two files make up the Exception class:
    udas_srv_exception.c
    udas_srv_exception.h
In prose and in design diagrams, the class-name is written with an initial capital and hyphens between words: e.g. Pixel-stream. This follows a common usage of UML and similar design notations.

In file-names, the initial capital is changes to lower case and the hyphens become underscores: e.g. udas_srv_pixel_stream.c. The abstract data-type that the class represents retains, in code, the initial capital but also changes the hyphens to underscores. For example:

    Pixel_stream p = new_pixel_stream (0);
This follows a common convention for C code.
 

Encapsulation

The public interface of an UltraDAS class consists in a definition of the abstract type, definitions of any public constants of the class, and the prototypes of the public methods. All these are in the class' header file.

All instances of the class are created dynamically, on the heap. The defined type of the object is actually a pointer type, e.g.:

    struct Pixel_stream {
        ...
    };
    typedef struct Pixel_stream* Pixel_stream;
By this technique, the attributes of the class are effectively public, because the structure definition has to be in the header, but the convention is to treat all attributes as private. An instance of the class should only be accessed wthrough one of its methods.

The implementation of  of an UltraDAS class consists in the public and private methods, together with any private constants. All these are in the class' single source-file.

In C, the methods are normal C functions with external linkage for the public methods and with the private methods declared static.

In C++, the compiler knows that methods of a class will always be called on an object of that class, and each class has a sperate name-speace for its methods. In C, the "methods" are ordinary function with extrnal linkage, so they share one name-space. Hence, the names of methods of UltraDAS classes embed the class name. For example the Clocks and Integrator classes each have a get_t_start method and these are distinguished in code as get_clocks_t_start and get_integrator_t_start.

In C++, the syntax for calling a method of an object is like this:

    Object o;
    o.some_method();
This is compliled a a call to a function some_method (or some mangled version of that name) with an implicit argument called this which is a pointer to the object o. C doesn't provide this neat notation, so methods of UltraDAS classes have an explict this pointer which callers have to pass in.  For example:
    void some_pixel_stream_method (Pixel_stream this)
    {
        ...
    }

Constructors and destructors

Objects are instantiated by calling a constructor. By convention, the constructor for my_class is called new_my_class, so the call is
    My_class m = new_my_class();
which is as close to the new operator in C++ as one can get in C. The constructor allocates the object on the heap, sets its attributes and may also acquire resources that the object owns throughout its lifetime. For example, the Fits-file class owns the file it represents, so the file is created and opened in the constructor.

All the objects are on the heap, so they have to be deallocated when they are no longer needed. Good OO languages do this "garbage collection" automatically; C++ and C do not. Note that an UltraDAS object is never held on a stack, so can never be an automatic variable in C. If you declare a variable of the object type as an automatic variable, you are declaring only a pointer, not the object structure.

In C++, the delete operator destroys an object created by the new operator. The class may define a destructor method that tears down parts of the object that would otherwise be released when the object dies: e.g. the destructor might close a file descriptor that would otherwise be leaked when the object's memory was reallocated. If there is a destructor, the compiler undertakes to have it called automatically when delete destroys the object.

C does not support the automatic calling of destructors, so the UltraDAS classes invert the C++ scheme. To destroy an UltraDAS object, one calls the destructor directly, and the destructor deallocates the object's memory.  For example:

    My_class m = new_my_class();
    ...
    m = end_my_class (m);
    ...
    if (m == NULL) m = new_my_class ();
By convention,  the destructor method for My-class is end_my_class. The destructor takes the object to be destroyed as an argument and returns a null pointer of the object's type. This allows the construction in the example above, where the main pointer to the object is annulled in the same statement that destroys it; it's a safety feature to reduce programming errors.

A destructor that is passed a null pointer returns immediately with no error reports.
 

Polymorphism, inheritence and aggregation

Inheritence is not provided at all in the UltraDAS scheme. C does not provide any support for it. In principle, the subclass and superclass could share structure via a C union, but this has not been exploited.

Aggregation - one object owning and enclosing objects of a different class - works well and is widely used. In fact, the system would not work without aggregation. Interestingly, many texts on object design now recomend more use of aggregation and less of inheritence than has been traditional in OO programming.

Polymorphism, which is often held to be the key concept of object-oriented systems, is unduly difficult in ANSI C. A function that expects to operate on the structure of one class will not happily accept the structure of another class, even with a pointer cast.

One special case of polymorphism does exist in UltraDAS. Almost all the classes know and use a generic Facade class that provides contact with the system outside the server programme. There are several kinds of facade that are logically subclasses of the generic facade; each provides the common methods of the superclass and many specialised methods; for example, there is a Camera-facade class that runs the DRAMA interface to udas_camera. This polymorphism only works because the generic Facade object is empty. None of the methods on the superclass use any attributes of the object and there is anyway only one Facade per programme, so the object pointer passed by the caller is ignored and the trick works.
 

Object ownership

Garbage collection is manual, as in C++. An object with no pointers left in scope has been leaked, and its resources are lost to the programme.

When an object must be manually destroyed, ownership of the object becomes important: it is a deadly error to call the object's destructor twice for two separate pointers to the object, and an even worse error to call the destructor while the object is in use. The object must have a single owner, and the owner is responsible for destroying the object when it is no longer needed.

Object ownership in the UltraDAS server generally follow one of these patterns:

These are discussed further in the section on design patterns.