home *** CD-ROM | disk | FTP | other *** search
Text File | 1989-10-03 | 15.7 KB | 428 lines | [TEXT/EDIT] |
- /* README - Portable C++/THINK C 4.0 Object programs
-
- Copyright (C) 1989, Integrity Software
- Author: Isaac J. Salzman (salzman@rand.org)
-
- This software may be freely used/modified/distributed
- as desired so long as this copyright notice remains
- in tact.
-
- $Header: /tmp_mnt/amnt/lh/salzman/src/class/RCS/README,v 1.2 89/10/03 00:32:58 salzman Exp Locker: salzman $
-
- $Log: README,v $
- # Revision 1.2 89/10/03 00:32:58 salzman
- # added note about virtual destructors
- #
- Revision 1.1 89/09/17 15:01:41 salzman
- Initial revision
-
- */
-
- The contents of this archive are as follows:
-
- README - this file
- class.h - macros for portable C++/THINK C object programs
-
- link.h - a Link class for implementing linked lists: header
- link.c - and implementation
- intlink.h - IntLink class - linked list of int's: header
- intlink.c - and implementation
- strlink.h - StrLink class - linked list of strings: header
- strlink.c - and implementation
- listlink.h - ListLink class - linked list of Link's: header
- listlink.c - and implementation
- linktest.c - a test program for the above classes
-
- All documentation is contained in this file. Please read this before
- attempting to use class.h. The macros are not nearly as important as
- the contents of this file.
-
- The enclosed "class.h" file contains a set of C pre-processor macros to aid
- in the development of source code that is portable between THINK C 4.0
- objects and C++. By using these macros and following certain coding
- conventions, source code may be compiled and run under THINK C 4.0 or C++
- without modification. These macros have been tested with GNU C++ 1.35,
- and Oasys C++ (which is basically a port of AT&T cfront 1.2). NOTE: The
- Oasys cpp is broken -- use the @n option with Oasys to use the standard
- cpp.
-
- If you start with the goal of portability in mind it is quite easy to
- accomplish. If you try and apply it to already developed C++ code or THINK
- C object code, you will no doubt encounter difficulties (more so when
- starting with C++).
-
- Before I get in to the macros, I just want to address some general
- issues of portability between THINK C Objects (TCO) and C++.
-
- First off, objects in TCO are always pointers - you can't just
- declare something:
-
- Class foo;
-
- you must declare it as
-
- Class *foo;
-
- and then call the initialization method in order to instantiate it:
-
- foo = MakeObject(Class)( /* args to init method/constructor */ );
-
- In C++ this is just like saying
-
- foo = new Class (/* args */ );
-
- Details on what's done in TCO are in the description of the relevant
- macros below. The main point: always use pointers to objects (if you
- want to be portable)!
-
- Method overloading: TCO doesn't have it. Yes, this is a bummer.
- Though it's one of the better ideas in C++, it's something you
- can live without if you want portability. Methods of the same name
- in derived classes must take the same type and number of arguments
- as defined in the super class in TCO. One way to deal with that is
- to use <stdarg.h> and declare things like this:
-
- #include <stdarg.h>
-
-
- int method ( ... );
-
- Of course you have to deal with parsing the arg's instead of having
- the compiler figure out what to do.
-
- Comments: only use /* */, TCO doesn't know about //.
-
- Header files: THINK C has <stdlib.h> which must be included if you're
- using routines that are part of the ANSI C defined standard library.
- You'll have to include different header files for C++ depending on
- implementation (<std.h> for g++ for example).
-
- O/S portability is a whole 'nother issue. Real Mac programs don't use
- stdio. Those are issues which you'll have to deal with. If someone wants
- to port the THINK C class library to C++ and X11, that would be great!!
-
- I've probably left some stuff out, but those are the important issues.
- And now on with the macros....
-
- ----------------------------------------------------------------------
-
- Description
-
- The definition for each macro is shown for both THINK C Objects (TCO), and
- C++ followed by a brief explanation of its usage.
-
- TCO: #define class struct
- C++: /* none */
-
- Simply define class to struct for TCO. TCO does not have a "class"
- keyword, (and in actuality, struct may be used in place of class in C++
- as well).
-
- TCO: #define virtual
- C++: /* none */
-
- There is no "virtual" keyword in TCO since all methods are virtual.
- Simply define this as nothing.
-
- TCO: #define PRIVATE
- #define PROTECTED
- #define PUBLIC
-
- C++: #define PROTECTED protected:
- #define PRIVATE private:
- #define PUBLIC public:
-
- TCO makes no distinction between private, public and protected
- members. All members are public according to TCO. These macros are to
- be used in place of the labels normally used in C++. Do NOT include
- the colon ":", it's part of the macro.
-
- TCO: #define ROOT : indirect /* this can also be : direct, read the THINK C
- manual for a desc. of the difference */
- C++: #define ROOT
-
- TCO determines if a struct is a class by looking at wether or not it
- inherits from something. ALL classes in TCO must inherit from
- *somthing*, otherwise they're just normal C structs. A root class in
- TCO inherits from either "indirect" or "direct". Details on this
- are explained in TC manual (it has to do with memory management and
- wether handles or pointers are used). When declaring a root class,
- declare as follows:
-
- class some_root_class ROOT
- {
- /* variables, method declartions */
- };
-
- TCO: #define INHERIT_PUBLIC
- C++: #define INHERIT_PUBLIC public
-
- C++ requires that you explicitly declare wether or not public members
- of a base class are to be made public in a derived class. The default
- (for C++) is that these become private members of the derived class
- (and therefor can not be called by clients of this class).
- This does nothing in TCO since everything is essentially public.
- Example usage:
-
- class Derived INHERIT_PUBLIC : BaseClass
- {
- /* stuff */
- }
-
- TCO: #define PREDEC_CLASS(Class)
- C++: #define PREDEC_CLASS class Class
-
- If you're defining classes that need to refer to themselves (common
- for doing linked lists and such). I don't think you really need this
- at all (and I haven't tested it really). Instead, just preceed members
- of a class which will have the same type as the class currently being
- defined, with "class". [yeah, it's confusing].
-
- [the following macros deal with constructors/initialization]
-
- TCO: #define DECL_INIT(Class) Class *Init##Class
- C++: #define DECL_INIT(Class) Class
-
- [NOTE: the ## in the macro has the effect of performing string
- catenation. this is a handy for declaring initialize methods that have
- unique names for each class, which makes life much easier].
-
- These macros are used for DECLaring constructors/initialize methods in
- C++/TCO respectively. Example usage:
-
- class Derived INHERIT_PUBLIC : BaseClass
- {
-
- /* stuff */
-
- DECL_INIT(Derived)(/* args */);
- };
-
- In C++ this will expand to:
-
- Derived(/* args */);
-
- In TCO this will be:
-
- Derived *InitDerived(/* args */);
-
- There's no special method for doing initialization in TCO. In C++
- you must declare a constructor which is called everytime you
- instantiate an object of that class. This happens automatically. We
- can duplicate this behavior by the use of this macro and others to
- follow. Also, notice that there is no return type for the C++
- constructor. It's implicitly defined as returning an object (or
- pointer to an object) of that class. It can't be redefined. In TCO we
- need to declare this as always returning a pointer to that object (you
- can't declare objects in TCO, just pointers to objects). More on this
- follows.
-
- TCO: #define DEF_INIT(Class) Class *Class::Init##Class
- #define INIT_SUPER(Class,args) Class::Init##Class args
- #define SUPER_INIT_ARGS(args)
- #define RETURN_THIS return this
-
- C++: #define DEF_INIT(Class) Class::Class
- #define SUPER_INIT_ARGS(args) : args
- #define INIT_SUPER(Class,args)
- #define RETURN_THIS
-
- These are used when actually defining the constructor/initialization
- method. Example:
-
- DEF_INIT(Derived)( /* args */ ) SUPER_INIT_ARGS(( /* super args */ ))
- {
- /* code */
-
- INIT_SUPER(BaseClass, (/* super args */));
-
- RETURN_THIS;
- }
-
- which ends up to be (for TCO):
-
- Derived *Derived::InitDerived ( /* args */ )
- {
- /* code */
-
- BaseClass::InitBaseClass (/* super args */);
- return this;
- }
-
- and this (for C++):
-
- Derived::Derived ( /* args */ ) : ( /* super args */ )
- {
- /* code */
-
- ;
- ;
- }
-
- Yes this looks kludgy, but it does the job! There are 4 macros
- involved here: DEF_INIT for defining the constructor/initialization
- method, SUPER_INIT_ARGS for passing arguments up to the super class
- (the thing that this inherits from -- only relevant to C++),
- and INIT_SUPER for calling the super classes's initialize method (only
- relevant to TCO). RETURN_THIS is needed for TCO since we're going
- to assign whatever is returned from the initialization method to
- the pointer to the object we're instantiating. You can just put
- "return this;" in there, but C++ will complain with a warning
- message.
-
- Initialization is where TCO and C++ diverge the most and where
- you need to be a bit kludgy and verbose in order to be portable. But,
- it's worth the effort! Depending on wether you're using C++ or TCO,
- only one of INIT_SUPER or SUPER_INIT_ARGS will actually result in any
- code being generated from the macros.
-
- Things to be careful about: you must enclose (args) in paren's as
- shown, or the macros will break. The last line in this method must be
- "RETURN_THIS;" in order for TCO initialization to work properly. You
- only need SUPER_INIT_ARGS if the super class takes arguments. This is
- one of the more arcane constructs of C++.
-
- TCO: #define MakeObject(Class) ((Class *)new (Class))->Init##Class
- C++: #define MakeObject(Class) new Class
-
- And here's how to make an instance of the object:
-
- Derived *dp; /* declare a ptr to the object */
-
- dp = MakeObject(Derived)( /* args */ ); /* instantiate it */
-
- which will end up as (in TCO):
-
- dp = ((Derived *) new (Derived))->InitDerived ( /* args */ )
-
- and (in C++):
-
- dp = new Derived ( /* args */ );
-
- TCO does not call any initialization method automatically upon
- instantiation as C++ does. But we can still do it in the same step, and
- hide the details in a macro. So as far as you're concerned, it always
- gets done. The syntax of calling the initialize method at the same
- time as instantiating the object in TCO is out of the TC User's
- Manual. But again, it's tucked away in a macro, so how it's done
- shouldn't really matter, right?
-
- [and now all the destructor/destroy method stuff]
-
- TCO: #define DECL_DEST(Class) void Destroy
- #define DEF_DEST(Class) void Class::Destroy
- #define DestroyObject(Object) { Object->Destroy();delete(Object); }
- #define DESTROY_SUPER inherited::Destroy()
-
- C++: #define DECL_DEST(Class) virtual ~Class
- #define DEF_DEST(Class) Class::~Class
- #define DestroyObject(Object) delete Object
- #define DESTROY_SUPER
-
- These macros define the destroy/destructor methods for TCO and C++.
- Example usage:
-
- class Derived : INHERIT_PUBLIC SuperClass
- {
- /* stuff */
-
- DECL_DEST(Derived)( /* args */ );
- }
-
- DEF_DEST(Derived)( /* args */ )
- {
- /* code */
-
- DESTROY_SUPER;
- }
-
- /* and to get rid of something */
-
- Derived *dp;
-
- DestroyObject(dp);
-
- These macros are pretty straight forward. Things to watch out for:
- I've chosen to call all destroy methods Destroy() in TCO. This means
- that they must always take the same number of and type of arguments
- for all derived classes. Since these methods rarely take arguments,
- I chose to stick with this convention. If you want to append the
- class name in TCO, no problem: just use Destroy##Class, but then
- you'd have to pass the class name as an argument as well....
-
-
- C++ automatically calls virtual destructors up the inheritance
- hierarchy. TCO doesn't (it's just another method in TCO),
- therefore you need to use DESTROY_SUPER in TCO -- which just
- calls the super classes Destroy() method, and does nothing in C++.
-
- NOTE: destructors MUST be declared virtual in C++ in order for
- them to have compatible behaviour with TCO. There are some
- obscure instances where complete compatability is impossible.
- If you call a virtual destructor for a derived class, it will
- work its way up the hierarchy calling destructors for each
- base class. As it leaves a destructor, the object does not belong
- to that class anymore. It changes its identity along the way. The
- result is that if you're calling virtual methods in your destructors,
- you will not get the expected results. Since, in TCO, the Destroy
- method is like any other method, it does not have this odd side
- effect. It turns out that Cfront 1.2 is broken in this respect
- whereas GNU C++ does the right thing (I found this out while
- reporting what I thought may have been a bug in g++).
-
- ------------------------------------------------------------------------
-
- The Example Program
-
- As an example, I've created a Link class which is used to create linked
- lists. This is not intended to be the worlds best or most efficient
- implementation of a linked list class, but I happen to find it useful
- for my work, and it serves as a good example for using the class.h macros.
-
- First off, the Link class is not to be used by itself, but to be inherited
- from. It implements some basic funtions: append, prepend, insert and find.
- Those names should be self explanitory. These need not be redifined for
- sub-classes, though you can if you want. There is a compare method which
- is used by find and insert. For simplicity, it always takes a string (char *)
- as an argument and is used to find a link by name. The showval method is also
- virtual and defined for sub-classes and is used to print the contents of
- the current link.
-
- A link has a next, previous and tail pointer (i.e. it's doubly-linked).
- In order for prepend to work properly, the first link (the head) never
- changes. That would require assigning to "this", which, as far as i'm
- concerned, is BAD news (and in fact not supported in cfront 2.0 or current
- g++ implementations). So the head link doesn't contain any useful data
- except for a pointer to the tail and a pointer to the real first link in
- the list (the next pointer).
-
- There are three sub-classes of Link: IntLink a list of integers, StrLink:
- a list of strings (char *'s), and ListLink: (which is derived from StrLink),
- a list of links. The example program creates 2 lists, each containing a
- mixture of StrLinks and IntLinks, and then creates a ListLink which contains
- those 2 lists. The lookup method (only in the ListLink class), is used to
- search for an item by name in any of the lists contained in the ListLink, or
- to search for the a list with that name. Yes this is confusing -- look at
- the source code!!!
-
- To build the example:
-
- - Use the Makefile for UNIX and C++.
-
- - Create a THINK C project with all the .c files and the
- ANSI and oops libraries.
-
- ----------------------------------------------------------------------------
-
- If you have any questions, feel free to send me e-mail. And, if you've got
- some suggestions and improvements, please send them along!! I hope someone
- finds this stuff useful. Enjoy!!
-
- * Isaac J. Salzman ----
- * The RAND Corporation - Information Sciences Dept. /o o/ /
- * 1700 Main St., PO Box 2138, Santa Monica, CA 90406-2138 | v | |
- * AT&T : +1 213-393-0411 x6421 or x7923 (ISL lab) _| |_/
- * Internet : salzman@rand.org / | |
- * UUCP : !uunet!rand.org!salzman | | |
- * CompuServe: 76167,1046
-