blob: e98d1fb28c77cead62ffc0e5cd7e61aa0ba42631 [file] [log] [blame]
/* Minimal object-oriented facilities for C.
Copyright (C) 2006 Free Software Foundation, Inc.
Written by Bruno Haible <bruno@clisp.org>, 2006.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
/* This file defines minimal facilities for object-oriented programming
style in ANSI C.
The facilities allow to define classes with single inheritance and
"virtual" methods.
Strict type checking is provided in combination with a C++ compiler:
The code compiles in ANSI C with less strict type checking; when
compiled with a C++ compiler, strict type checking is done.
In contrast to [OOC] and [OOPC], this implementation concentrates on the
bare essentials of an object-oriented programming style. It does not
provide features that are "sometimes useful", but not essential.
Features:
- Combination of fields and methods into a single object. YES
- Description of objects of same shape and same behaviour
by a class. YES
- Single inheritance. YES
- Multiple inheritance. NO
- Operator overloading (compile-time polymorphism). NO
- Virtual methods (run-time polymorphism). YES
- Information hiding: private/protected/public. private fields
- Static fields and methods. NO
- Constructors, destructors. NO
- 'new', 'delete'. NO
- Exception handling. NO
- Garbage collection. NO
- Templates / Generic classes with parameters. NO
- Namespaces. NO
- Hidden 'this' pointer in methods. NO
- Declaring or implementing several classes in the same file. NO
Rationale for NO:
- Multiple inheritance is not supported because programming languages
like Java and C# prove that they are not necessary. Modern design
patterns use delegation more often than composition; this reduces
the pressure to use multiple inheritance.
Multiple inheritance of "interfaces" (classes without fields) might
be considered, though.
- Operator overloading is not essential: The programmer can rename
methods so that they carry unambiguous method names. This also makes
the code more readable.
- Virtual methods are supported. Non-virtual methods are not: they
constitute an assumption about the possible subclasses which is more
often wrong than right. In other words, non-virtual methods are a
premature optimization - "the root of all evil", according to
Donald E. Knuth.
- Information hiding: 'protected' is not supported because it is always
inappropriate: it prohibits the use of the delegation design pattern.
'private' is implemented on fields. There are no 'public' fields,
since the use of getters/setters allows for overriding in subclasses
and is more maintainable (ability to set breakpoints). On the other
hand, all methods are 'public'. 'private` methods are not supported
because methods with static linkage can be used instead.
- Static fields and methods are not supported because normal variables
and functions with static or extern linkage can be used instead.
- Constructors and destructors are not supported. The programmer can
define 'init' and 'do_free' methods himself.
- 'new', 'delete' are not supported because they only provide the
grouping of two lines of code into a single line of code.
- Exception handling is not supported because conventions with a return
code can be used instead.
- Garbage collection is not supported. Without it the programmer's life
is harder, but not impossible. The programmer has to think about
ownership of objects.
- Templates / Generic classes with parameters are not supported because
they are mostly used for container classes, and container classes can
be implemented in a simpler object-oriented way that requires only a
very limited form of class inheritance.
- Namespaces are not implemented, because they can be simulated by a
consistent naming convention.
- A hidden 'this' pointer in methods is not implemented. It reduces the
transparency of the code (because what looks like a variable access can
be an access through 'this') and is simply not needed.
- Declaring or implementing several classes in the same file is not
supported, because it is anyway good practice to define each class in
its own .oo.h / .oo.c file.
Syntax:
The syntax resembles C++, but deviates from C++ where the C++ syntax is
just too braindead.
A root class is declared in a .oo.h file:
struct rootfoo
{
methods:
int method1 (rootfoo_t x, ...); ...
};
and in the corresponding .oo.c file:
struct rootfoo
{
fields:
int field1; ...
};
A subclass is declared in a .oo.h file as well:
struct subclass : struct rootfoo
{
methods:
int method2 (subclass_t x, ...); ...
};
and in the corresponding .oo.c file:
struct subclass : struct rootfoo
{
fields:
int field2; ...
};
This defines:
- An incomplete type 'struct any_rootfoo_representation' or
'struct subclass_representation', respectively. It denotes the memory
occupied by an object of the respective class. The prefix 'any_' is
present only for a root class.
- A type 'rootfoo_t' or 'subclass_t' that is equivalent to a pointer
'struct any_rootfoo_representation *' or
'struct subclass_representation *', respectively.
- A type 'struct rootfoo_implementation' or
'struct subclass_implementation', respectively. It contains a virtual
function table for the corresponding type.
- A type 'struct rootfoo_representation_header' or
'struct subclass_representation_header', respectively, that defines
the part of the memory representation containing the virtual function
table pointer.
- Functions 'rootfoo_method1 (rootfoo_t x, ...);' ...
'subclass_method1 (subclass_t x, ...);' ...
'subclass_method2 (subclass_t x, ...);' ...
that invoke the corresponding methods. They are realized as inline
functions if possible.
- A declaration of 'rootfoo_typeinfo' or 'subclass_typeinfo', respectively,
each being a typeinfo_t instance.
- A declaration of 'ROOTFOO_SUPERCLASSES' or 'SUBCLASS_SUPERCLASSES',
respectively, each being an initializer for an array of typeinfo_t.
- A declaration of 'ROOTFOO_SUPERCLASSES_LENGTH' or
'SUBCLASS_SUPERCLASSES_LENGTH', respectively, each denoting the length
of that initializer.
- A declaration of 'rootfoo_vtable' or 'subclass_vtable', respectively,
being an instance of 'struct rootfoo_implementation' or
'struct subclass_implementation', respectively.
- A header file "rootfoo.priv.h" or "subclass.priv.h" that defines the
private fields of 'struct rootfoo_representation' or
'struct subclass_representation', respectively.
A class implementation looks like this, in a .oo.c file:
struct subclass : struct rootfoo
{
fields:
int field2; ...
};
int subclass::method1 (subclass_t x, ...) { ... } [optional]
int subclass::method2 (subclass_t x, ...) { ... }
...
At the place of the second "struct subclass" definition, the type
'struct subclass_representation' is expanded, and the macro 'super' is
defined, referring to the vtable of the superclass. For root classes,
'super' is not defined. Also, 'subclass_typeinfo' is defined.
Each method subclass::method_i defines the implementation of a method
for the particular class. Its C name is subclass__method_i (not to be
confused with subclass_method_i, which is the externally visible function
that invokes this method).
Methods that are not defined implicitly inherited from the superclass.
At the end of the file, 'subclass_vtable' is defined, as well as
'subclass_method1 (subclass_t x, ...);' ...
'subclass_method2 (subclass_t x, ...);' ...
if they were not already defined as inline functions in the header file.
Object representation in memory:
- Objects have as their first field, called 'vtable', a pointer to a table
to data and function pointers that depend only on the class, not on the
object instance.
- One of the first fields of the vtable is a pointer to the
'superclasses'; this is a NULL-terminated array of pointers to
typeinfo_t objects, starting with the class itself, then its
superclass etc.
[OOC] Axel-Tobias Schreiner: Object-oriented programming with ANSI-C. 1993.
[OOPC] Laurent Deniau: Object Oriented Programming in C. 2001.
*/
#ifndef _MOO_H
#define _MOO_H
/* Get size_t, abort(). */
#include <stdlib.h>
/* An object of this type is defined for each class. */
typedef struct
{
const char *classname;
} typeinfo_t;
/* IS_INSTANCE (OBJ, ROOTCLASSNAME, CLASSNAME)
tests whether an object is instance of a given class, given as lower case
class name. */
#define IS_INSTANCE(obj,rootclassname,classname) \
(((const struct rootclassname##_representation_header *)(const struct any_##rootclassname##_representation *)(obj))->vtable->superclasses_length \
>= classname##_SUPERCLASSES_LENGTH \
&& ((const struct rootclassname##_representation_header *)(const struct any_##rootclassname##_representation *)(obj))->vtable->superclasses \
[((const struct rootclassname##_representation_header *)(const struct any_##rootclassname##_representation *)(obj))->vtable->superclasses_length \
- classname##_SUPERCLASSES_LENGTH] \
== & classname##_typeinfo)
/* This instance test consists of two comparisons. One could even optimize
this to a single comparison, by limiting the inheritance depth to a fixed
limit, for example, say, depth <= 10. The superclasses list would then
need to be stored in reverse order, from the root down to the class itself,
and be filled up with NULLs so that the array has length 10. The instance
test would look like this:
#define IS_INSTANCE(obj,rootclassname,classname) \
(((const struct rootclassname##_representation_header *)(const struct any_##rootclassname##_representation *)(obj))->vtable->superclasses \
[classname##_SUPERCLASSES_LENGTH - 1] \
== & classname##_typeinfo)
but the classname##_superclasses_length would no longer be available as a
simple sizeof expression. */
#endif /* _MOO_H */