Office Source Code Style Guide
Dave Parker, 6/30/95
Abstract
This document outlines a general style guide for C and C + + source code in Office development. The main purpose here are to list features of C + + which we'll use and which we'll avoid, along with the basic rationale for doing so. There is also standards for basic coding issues for the sake of consistency within the code and robust constructs. A complete list of C + + language features with commentary. Rather, it mentions only the issues we consider important. Knowledge of C + + is assumed.
Contents
1. General Goals 3
2. CLASSES 3
2.1 CLASS VS. STRUCT 4
2.2 Public, PRIVATE, and PROTECTED members 4
2.3 DATA Members 4
2.4 VIRTUAL FUNCTIONS 5
2.5 Constructors 5
2.6 Destructors 6
2.7 NEW and DELETE 7
2.8 OPERATORS 7
2.9 Inheritance 8
2.9.1 inheritance of Interface vs. implementation 8
2.9.2 Inheritance vs. containment 10
2.9.3 Multiple Inheritance 11
3. Other C + + FEATURES 11
3.1 CONSTANTS and Enumerations 12
3.2 REFERENCES 12
3.3 CONST PARAMETERS and FUNCTIONS 13
3.4 DEFAULT ARGUMENTS 13
3.5 FUNCTION Overloading 14
3.6 OPERATOR Overloading 14
4. COMMON/C + + issues 14
4.1 #IFDEFS 14
4.2 GLOBAL VARIABLES 15
4.3 MACROS and INLINE FUNCTIONS 16
4.4 Optimization 16
4.5 WARNINGS 17
4.6 PRIVATE DATA and FUNCTIONS 17
4.7 TYPEDEFS 17
4.8 BASIC DATA TYPES 17
4.9 Pointers 18
4.10 SWITCH Statements 19
4.11 ASSERTS 19
4.12 ERRORS and EXCEPTIONS 19
5. Formatting Conventions 20
5.1 Naming conventions 20
5.2 FUNCTION Prototypes 21
5.3 VARIABLE DECLARATIONS 22
5.4 CLASS DECLARATIONS 22
5.5 COMMENTS 23
5.5.1 File Headers and section separators 23
5.5.2 Function Headers 24
5.5.3 In-code Comments 25
5.5.4 Attention Markers 25
5.6 MISC. Formatting Conventions 26
5.7 SOURCE FILE ORGANIZATION 27
5.7.1 Public Interface Files 27
5.7.2 Private Interface Files 28
5.7.3 Implementation Files 28
5.7.4 Base Filenames 29
6. INTERFACES to DLLS 29
6.1 C FUNCTIONS and GLOBAL VARIABLES 29
6.2 COMMON/C + + public HEADER FILES 29
6.3 Lightweight COM OBJECTS and Isimpleunknown 30
7. Appendix A:basic Hungarian REFERENCE 33
7.1 MAKING Hungarian NAMES 33
7.2 Standard BASE TAGS 33
7.3 Standard Prefixes 34
7.4 Standard QUALIFIERS 35
1. General goals
C + + is a complex language this provides many ways to does things, and going 搘 hole Hog "on all of the its features can leads to co NFusion, inefficiency, or maintenance problems. All Office developers need to become experts on the features we'll use, and avoid the others in order to form solid conv Entions within the group that we is all comfortable with. Our use of C + + features would be fairly conservative. We management much rather err on the side of just dealing with C, which we youre e all used to, then screwing up our app with a new CONCEP T that isn't all of us is used to.
Underlying the choice of all of the style decisions is a few basic goals, as listed below. When in doubt on a particular issue, always think about the spirit of these goals. Sometimes these goals would conflict, of course, and these cases we try to either prioritize the tradeoffs or use Experi ence (either our own, or from other groups, that has used C + + extensively).
1. Simplicity. When in doubt, keep it is simple. Bugs is related mostly to complexity, not code.
2. Clarity. The code should does what it looks like it world抯 doing. Other people need to is able to understand your code.
3. Efficiency. Speed and size is important. Using C + + does not imply big and slow. There is plenty of perfectly reasonable ways to make things as fast or faster than the normal C-to. Speed and size often trader off, and most people probably err in the side of choosing speed too often. Remember that 20% of the code was responsible for 80% of the time. In the most cases, we youre e more concerned about fitting comfortably in less RAM.
4. Appropriateness. Use the language construct, that's appropriate for the abstraction, or operation you's trying to do. Do not abuse the language. Don don't use a construct just because it happens to work. Definitely don don't use a strange construct to amaze and confuse your friends to try to show how smart is.
5. Natural transition from C to C + +. We all used to be C programmers. Others that look at our code is still C programmers (e.g. Word and Excel). When possible, avoid C + + constructs where a C programmer world抯 instinct causes a wrong assumption.
6. Catch Errors Early. Having the compiler catch a error is ideal. Having debug code (e.g Asserts) catch it's the next best thing, etc. Declare things in such as a to give the compiler the best chance at catching errors.
7. Fast builds. Total generality and modularity can cause lots of inter-dependencies between files, which can has a dramatic impact on BU ILD Times. This was a constant time sink for everyone. It is often worth rearranging things a little to make incremental builds faster.
8. Consistency. The whole point's have a style guide was that programmers be never totally autonomous, even when the group had strong C Ode ownership. Other people need to read and understand your code. Everyone has to give a little to having a consistent style guide, but everyone gains it back when they read or debug other P Eople World抯 Code.
2. Classes
C + + classes is a nice-to-encapsulate code and data into a single unit, which provides a good paradigm for Object-orie Nted implementations as well other features such as flexible access control, convenient and Type-safe polymorphism, and th e possibility of code reuse via inheritance.
At the more general, classes is an extension to the built-in typing of C which allows you to define your own types along With the operations in that type. Taken to the extreme, every piece of data in a program could is an instance of a class. However, we'll not go to nearly this is in Office. We'll use classes when there are a good reason to, such as the concept being implemented are inherently object-oriented or Polymorphism is required. It has been the experience of many people, that programs, use, classes for everything evolve to systems that is compl Ex and inefficient. Although this is not being the fault of any particular class, complex class hierarchies can leads to needless complexity, and Overly abstracted concepts can easily leads to inefficiency.
In general, we'll avoid allocating classes on the stack and passing classes by value, because this is where the use of C Onstructors and Destructors gets you to the most trouble. Most classes should is allocated via new, freed by delete, and passed by pointer. In addition, we'll never declare a global variable which is an instance of a class that have a constructor, because this Causes a bunch of C runtime stuff to get linked in and stuff to happen at boot time to construct the thing, which is a big Performance hit. Using only heap-allocated classes implies we basis l probably use classes only for relatively complex objects so you would nor Mally has the in the heap anyway, and not simple things like basic data types. Beyond this, it's a judgment call if to use a class. Use one if there are a good reason, but the if a more straightforward solution is just as good.
Summary:
use classes to encapsulate the implementation of a object-oriented concept.
use classes to implement polymorphism.
avoid allocating class instances on the stack and passing them by value. Use the new and delete, and pass them by pointer. This implies is not using the classes for simple data types.
never declare a global instance of a class that has a constructor.
not everything is as class. Use them if you gain something.
2.1 Class vs. Struct
In C + +, a struct can also has member functions and operators and everything else that a class can has. In fact, the-difference between a class and a struct is, the all members of the default to public access in a struct but PRI Vate access in a class. However, we won't use this as the deciding point between using a class vs. a struct. To match the normal intuition, we'll use a class if and only if there is member functions included.
Summary:
use a class instead of a struct if and only if there is member functions.
2.2 Public, Private, and Protected members
As stated above, structs default to public access and classes default to private access. However, we'll depend on the default is only in the case of structs (where we leave all the data implicitly public). For a class, we'll declare all members (both data and code) explicitly as public, protected, or private, and group them into sections on that order. For example:
Class Foo
{
Public
Foo ();
~foo ();
void Hey (int I);
void Ack ();
Protected
int m_ivalue;
Private
int M_istuff;
void Localhelpersub ();
};
Summary:
declare all classes explicitly as public, protected, or private, in groups on that order.
2.3 Data Members
Data members should use the naming convention M_name where name is a normal Hungarian local variable name. This makes member function implementations easier to read (no confusion on member vs. local data), and allows the use O f the same Hungarian name for, e.g, parameters and members. See the example below.
Data members should normally is declared public because this usually defeats the purpose of the class abstraction. To efficiently export a data member, declare inline get and set member functions. This would get optimized into the same code as a public data member. For example:
Class Counter
{
Public
int CItems () const {return m_citems;}
void Setcitems (int cItems) {m_citems = CItems;}
Private
int m_citems;
};
Summary:
data members use the naming convention M_name.
do not declare public data. Use the inline accessor functions for performance.
2.4 Virtual Functions
Virtual functions is used to allow derived classes to override a method in a base class by providing their own implementa tion in a-on-the-causes-most-derived version to be-called whenever a method is called through an object point Er, even if that pointer are declared as a pointer to the base class. This is usually do to implement polymorphism, and that world抯 if we basis l use them. For example, all COM interface methods is virtual because you is always going for polymorphism via a standard interface.
Unlike simple member functions, virtual functions incur some overhead due to need to call through the vtable. If A class contains at least one virtual function and the data size of each instantiated object would be 4 bytes larger th The combined size of the declared data in order-to-hold the vtable pointer. After the first virtual function, each additional one has adds another entry to the class vtable, which is static and per -class (Nothing per object), so the main concern this is whether a class have any virtual functions at all. In addition to the memory overhead, there are the overhead to indirect a pointer twice before calling the function. This was fairly fast and compact in 32-bit code, but affects speed and size nevertheless. Perhaps the worst part was that virtual functions cannot was inlined, so there would always be a function call, even when the Work is trivial.
Because They has overhead, you should don't use virtual functions in a class unless you need to. However, make sure Y ou do use them when it makes sense. in particular, if you had a base class which requires a destructor, then the de Structor should definitely be virtual to allow derived classes to destruct any added members properly. If the Destru ctor were not virtual, then in a context where polymorphism are being used (so the object pointer are declared as a pointer to the base class), the base class destructor'll always get called, even for an object of a derived class that added dat A members and declared their own destructor in an attempt to free them. the derived class World抯 destructor would only get CA lled if the base class destructor is declared virtual. This scenario applies to many other kinds of methods Would add to your classes. in fact, most of the methods in a base class might being this-if polymorphism is Intende d. This issues isdiscussed in + detail in the inheritance section below.
Note that although virtual functions has a performance penalty over regular member functions, they is often the most EFF Icient-Implement a concept such as polymorphism where the alternative would is large switch statements (not to ment Ion the benefits of the object-oriented encapsulation).
Summary:
use virtual functions to implement polymorphism.
virtual functions has overhead, so don don't use them unless you really should.
a destructor in A base class should always being virtual if polymorphism is intended.
2.5 constructors
Ah, constructors. Every new C + + programmer World抯 nightmare. This was one reason to try to minimize the use of constructors--C programmers aren don't used to them and would get confused. Another reason is the infamous performance overhead of calling a function (unless it world抯-inline) and doing work at possibly unexpected and/or redundant times.
However, using constructors can eliminate the dangers of uninitialized data and can also made the code simpler to read (if Youre e used to it). Judicious use of destructors (see below) which match the constructors can also help prevent memory leaks and other RESOURC E management problems.
Fortunately, the issue is mainly one when classes be declared on the stack or passed by value, both of which we'll avoi D. Most of our classes should is dynamic memory objects which would be passed around by pointer. In this case, the constructor are essentially just a helper function for the functions that create these dynamic objects. Using a constructor for this purpose are reasonable to ensure a clean and consistent initialization (if do sure to I Nitialize all data members), but to prevent potential performance problems due to redundant initialization the constructor Should not does anything expensive. Simply assigning a constant or a parameter value to each data field is about right. Very Simple constructors can is made inline.
Most importantly, a constructor should never is able to fail, because lacking a fancy exception handling mechanism, the CA Ller has no-to-handle this in some cases. Any initialization that can fail (e.g memory allocations) should is put in a separate initialization member function (CAL LED, e.g, finit). When the "is" the case, it's often useful to encapsulate the creation of an object in a function (a global function or a M Ember of another class) that calls new and then finit for the object, and returns the result of Finit. For example:
Class Foo
{
Public
Foo (int clines) {m_hwnd = NULL; m_clines = Clines}
Virtual ~foo ();
BOOL finit ();
void DoSomething ();
Private
HWND m_hwnd;
int m_clines;
};
BOOL fcreatefoo (int clines, Foo **ppfoo)
{
if ((*ppfoo = new Foo (clines)) = = NULL)
return FALSE;
if (*ppfoo->finit ())
return TRUE;
Delete *ppfoo;
*ppfoo = NULL;
return FALSE;
}
BOOL Foo::finit ()
{
M_hwnd = CreateWindow (...);
Return (m_hwnd! = NULL);
}
Summary:
do not does expensive work in a constructor.
if a constructor, make sure to initialize all data members.
very simple constructors can be made inline
a constructor should never fail. Do memory allocations and other potential failures in an finit method.
consider making a creation function that encapsulates the new and finit operations.
2.6 Destructors
If A class has resources, the need to being freed, then the destructor was a convenient place-to-put this. The normal case for us would be are just the central place to free resources for an object which is freed via Delet E (see below). The trickier use of destructors are for stack-allocated classes, but we youre e going to avoid so by does using classes on the S Tack.
A destructor should is careful to destroy an object properly regardless of what it was created or used. Furthermore, if you choose to implement a method of frees any resources before the actual destruction, make sure to Rese t those fields (e.g. set pointers to NULL) so that a destructor would not try to free them twice. It isn't necessary for the destructor to reset any fields, though, because the object cannot being used after it is destruct Ed.
Like a constructor, a destructor can never fail. Also, as stated above, a destructor in a base class should always being declared virtual to make polymorphism work.
The destructor for the above example would is defined as:
Foo:~foo ()
{
if (m_hwnd! = NULL)
DestroyWindow (m_hwnd);
}
Summary:
use a destructor to centralize the resource cleanup of a class which is freed via delete.
if Resources is freed before destruction, make sure the fields is reset (e.g. set pointers to NULL) so that a destruct Or would not be try to free them again.
a destructor should never fail.
a destructor in A base class should all is declared virtual if polymorphism might be used.