udas_srv_exception.c
udas_srv_exception.hIn 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.
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) { ... }
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.
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.
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: