|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Guildhall Coding Conventions The coding conventions I use for developing Guildhall are strongly influenced by practices developed by the Pink (Apple) and Taligent engineering teams. You can read about those practices if you can find a copy of Taligent's Guide To Designing Programs: Well-Mannered Object-Oriented Design in C++, edited by D. Goldsmith, published by Addison-Wesley in 1994. That said, I'm quite sure the contributors would be horrified by the manner in which I've subverted some of those practices, but that keeps life exciting, right? Since you aren't likely to find a copy of that work, and since I've politely given its authors credit, I'll just get on with telling you how I do things. General Guidelines C++ is based, of course, on the C programming language, and it inherits some generally problematic conventions. So here are some syntax elements that C allows that I explicitly avoid.
And then there's C++, which also has some syntax elements that I religiously avoid.
So, what about conventions I do use? Here are a few.
Scalars One aspect of cross-platform development that I find frustrating is the platform differences for scalar types. Not only can they change from one host to another, they can change when the host OS makes changes. Apple did this a bunch of years back in its transition to 64-bit architectures. From my perspective it's useless. The memory organization isn't changing, and while some of these modified types are dictated by changes in evolving C/C++ language specifications, they're detrimental to maintaining working code. With that in mind, I opted for the following simplified scheme which I overlay on top of what's provided by any host system I support.
In addition to these, I have a few pseudo scalar types that come into play when handling the four types of enum declarations, meaning having some signed items versus having exclusively unsigned items, and fitting within a 32 bit integer versus requiring a 64 bit integer.
Memory Management A mostly static class called THeap, declared in the Primitives section, is responsible for tracking all memory management in Guildhall. It does this by virtue of the convention that every class that can be allocated by new must override the new and delete operators in the following fashion.
I adopted this scheme before RTTI was settled, and it has served me quite well. The THeap code tracks a lot of information about the pointers created as Guildhall runs, and automatically reports memory leaks. The code guards against any pointers being allocated before static initialization is complete and after static termination has begun. It writes type IDs and the requested pointer length into a header tacked onto the front of every pointer and puts scribble guards both before and after the pointer data. If need be the class can also enable a great deal of instrumentation to help track down difficult pointer issues, for example by checking every pointer for scribbling. But such problems have become quite rare. In fact, if I do get a random crash the first thing I check is to see whether the program stack has been overrun somehow. It seems to me that the runtime Guildhall most often runs upon ought to be quite a bit more graceful about that sort of thing, but it isn't. One other comment about memory management, and this is solely a matter of personal preference. I happen to despise non-deterministic coding architectures, and I explicitly refer to garbage collection. I'm not referring to what the memory system has to do to organize and recover deallocated pointers, more the process of ignoring unreferenced pointers, assuming the system will come along eventually and clean things up. I try to carefully deallocate every pointer I create, and I have plenty of tools in place to assist with that. Moreover, I will zero out pointers in the destructors of classes, even if those pointers were just aliases. If there's a dangling reference out there I want it to crash early and often so I can find it and get rid of it. That was another Taligent design principle, getting bugs to crash early and often, and it's a fine one. In a similar way, the ordering of constructor calls during static initialization is essentially non-deterministic. I solve this by causing all heap allocations to fail if static initialization is not complete. I have tools that can defer heap-based initialization until after the heap is ready. Persistent Classes To fit within the Guildhall architecture, classes which encapsulate persistent data must implement a suite of standard methods. The following sample class provides an overview.
GetStaticType is used for a number of purposes, but its role in memory management is certainly the most prominent. CopySelf is specifically not a virtual function. There is an analogous Clone() function for polymorphic classes. CopySelf should never be implemented in Abstract or Mix-in classes. AStream is the abstract class that stands in for several persistent stores, including binary host files, Guildhall archive files, and the special memory-based object TMemoryStream. The paired operators >>= and <<= write and read, respectively, a binary representation of TClass. Support routines enable the streaming of scalars, including enums. For class based members, the same operators implemented in the member classes provide streaming support. Polymorphic members have additional support, not described here, that allow such members to be properly stored and retrieved. ATextFile is the abstract class that stands in for text based stores, including host text files and Guildhall archive text files. The text output for Export and Import is similar to XML, or at least as close as I could get it—I didn't study the XML conventions. If it's important I could probably make it conform. It hasn't yet been important. The purpose of these methods is a sort of fail safe against insurmountable upgrade issues. If I can at least get the data out in XML form I can write something to reconstitute it for a different system. And although I wrote the methods as Export and Import, I really wish I had used the operators for >> and <<. It would have had a nice symmetry. Maybe someday I'll convert to those operators. Finally, the Compare methods are solely for debugging. The first one typically uses some standard Guildhall calls to check the pointers for NIL and call the second one if it has two non-NIL pointers. The second one should iterate across each pertinent member variable, relying upon helper classes for scalars and upon embedded Compare calls for class-based members. Templates While I'm sure this will horrify C++ afficionados, I do not use any C++ standard library templates. Instead I use home-grown templates. Following the Taligent convention, I almost always use C++ templates as a thin layer over constructible helper classes. The constructible helper class does the work, usually by manipulating pointers, and the template handles type conversion. I have a few such constructible classes that have been working well for a long time. By convention, my template declarations start with X. The most prominent helper classes are TPointerArray, which manages an embedded array of void*, and TNamedPointerArray, which subclasses from TPointerArray and adds the ability to index its void* pointers with strings (as TString). XPointerArray and XNamedPointerArray are, respectively, built upon those two classes, converting the managed pointers from type void* to TClass*. So XPointerArray
Other Conventions Other conventions will be discussed as necessary to explain each part of the Guildhall architecture. |