Object-oriented techniques in C
Dave Jones
djones at megatest.UUCP
Tue May 24 10:54:30 AEST 1988
in article <684 at vsi.UUCP>, friedl at vsi.UUCP (Stephen J. Friedl) says:
>
> Hi.ho net.folks,
>
> I have never used C++ or any other object-oriented language,
> but have read enough about them to know they are a direction I
> wish to go. Our understanding of these techniques do not
> necessarily require an OO language; we are doing some of them in
> C. Are there any references to object-oriented techniques in C?
>
> Related to this, what is the best way to use C++ on the 3B
> family of machines? Any native code compilers? Translators?
>
> Thanks,
> Steve
>
> --
> Steve Friedl V-Systems, Inc. (714) 545-6442 3B2-kind-of-guy
> friedl at vsi.com {backbones}!vsi.com!friedl attmail!vsi!friedl
C++ is a big win. Especially if your C compiler, like the one I use,
does not have function prototypes.
The biggest wins that C++ give you are
1. convenience.. It's much easier, and less error-prone to let
cfront write all that code, rather than writing it "by hand";
2. inspiration.. You're more likely to write good (object-oriented)
code if the language inspires you to do so.
Still, almost anything you can do in C++, you can do in C. (Proof: cfront
generates C-code.) It's true that there is no automatic procedural
initialization of static variables in C. But that doesn't work too well in
the current implementation of C++ either. And macros aren't as good as
inline procedures. Etc., etc... But for the most part, if cfront can
write it, you can write it.
Here is a document which I, aided and abetted by my cohorts, have prepared
for a project which is just getting started. I had already evolved
approximately this style over the years, before I had ever heard the
expression "object-oriented". When C++ came out, I shamelessly stole
some ideas and modified the style somewhat to be more analagous to C++.
(Thanks, Bjourne.)
C CODING CONVENTIONS:
Many functions update only one kind of object. Other functions
serve only to inspect one kind of object and return appropriate results.
Such functions -- those closely related to one kind of object -- are called
"methods" of that type of object. A familiar example is the
stdio package in the C library. Those functions all deal with
a data structure called a "FILE".
As far as is practical, we will access the contents of an object only by
means of the object's methods. The large majority of our functions will
be methods of some type of object.
There are two typical kinds of .h files. One defines a type of
object, by giving structure-declarations for the object, and
extern-declarations for the methods of that type of
object. The other defines a procedure-table ("vtable") which
abstracts the procedures for a class of types of objects which
have similarly parameterized methods.
Notice that an object is defined as a structure, not as a pointer to a
structure.
Include-files protect themselves against multiple inclusion,
with an #ifndef, and should include all (protected) include-files which
their declarations require.
All methods should have extern declarations in the .h file.
Instance variables should be marked "read-only" or "private" when
appropriate, to warn users against setting or inspecting them.
Declarations which are only to be used for private instance-variables
should be marked "private".
#ifndef C_FOO_H
#define C_FOO_H
#include "Gar.h"
/* private */
typedef struct
{
char* bar;
}Bar;
/* public */
typedef struct
{
/* public */
int fleeble;
/* read-only */
int var;
/* private */
Bar fly;
Gar gar;
} Foo;
extern Foo* Foo_init();
extern void Foo_put();
#endif C_FOO_H
Method names are conventionally prefixed by the name of the class,
as in Foo_next() and Foo_put(), etc.. The prefix should be spelled
exactly the same and have the same case as the type-name (capitalized).
This will prove useful in doing global searches and replaces, in
writing macros, etc..
All methods of Foo have as a first parameter a variable of type Foo*,
conventionally called 'obj'. The preceding applies to initializers,
which conventionally are named Foo_init(), and to cleanup-routines,
conventionally named Foo_clean(). Initializers and cleaners return a
pointer to the object they operated on (obj). Cleanup routines are
intended to deallocate memory referenced by the object, but not
referenced by any other object.
To allocate an object from heap, use the NEW() macro from generic.h
as follows:
Foo* obj = Foo_init(NEW(Foo), arg1, arg2, argn);
#define NEW(type) ((type*)smalloc(sizeof(type)))
smalloc is a "safe" malloc, which calls an exception-handler when
it cannot allocate memory. The handler can be dynamically re-bound
to other procedures, but the default one writes an error message
to file-descriptor 2, and terminates the program. If you set the
handler to NULL, smalloc will behave like malloc(). See smalloc.h.
To dispose of an object obtained by smalloc, you can do this:
sfree((Ptr)Foo_clean(obj));
A class named Foo_iter is an iterator-class for class Foo.
Aside from the name "obj", "init", and other standard abbreviations which
we may agree upon and register, all variable-names will be spelled out
completely, with underscores separating words. Macros will be all
caps. Exception: Methods which, for speed, are coded as macros will
be spelled as though they were proper functions. Variable names and
general functions will be lower case. Method names will be capitalized,
as are object-type names.
Some objects need to contain pointers to functions. For example,
the hash-table package, "Assoc", uses functions "hash", "equiv", etc.,
which vary depending upon the type of object being stored in the
hash-tables. We will package related functions together into
structures called "vtable"'s. That stands for "virtual function
table". "Virtual" seems like a misnomer to me, but that's the
keyword that C++ uses, so I figure that it will come to have
some recognition value.
For example, class Assoc uses a vtable, which is defined in keys.h as
follows:
#ifndef _KEYS_H_
#define _KEYS_H_
#include "generic.h"
/* These are the operations which one needs to do on objects in
** order to use them as "keys" in lookup tables, etc.
*/
typedef struct
{
/* Assert: for all (obj1,obj2),
**
** !equiv(obj1,obj2) || (hash(obj1) == hash(obj2))
**
** and for all obj,
**
** equiv( obj, copy(obj))
*/
int (*hash)(/*Ref*/);
Ptr (*copy)(/*Ref*/); /* Given a Ptr to one, returns a Ptr.
* For some classes, copy() will simply return
* the Ptr which is given to it, and
* then delete() will do nothing.
* For other classes it will be a "deep copy"
*/
void (*delete)(/*Ptr*/); /* Deletes a key built by copy() */
Bool (*equiv)(/*Ptr, Ptr*/); /* Tells is two are equivalent */
}Key_functions;
#endif _KEYS_H_
More information about the Comp.lang.c
mailing list