Abstract
This document outlines a general style guide for C and C++ source code in Office Development. The main purpose here is to list features of C++ which we will use and which we will avoid, along with the basic rationale for doing so. There are also standards for basic coding issues for the sake of consistency within the code and robust constructs. This is not a complete list of C/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/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/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 that provides many ways to do things, and going 搘hole hog” on all of its features can lead to confusion, inefficiency, or maintenance problems. All Office developers need to become experts on the features we will use, and avoid the others in order to form solid conventions within the group that we are all comfortable with. Our use of C++ features will be fairly conservative. We抎 much rather err on the side of just dealing with C, which we抮e all used to, then screwing up our app with a new concept that not all of us are used to.
Underlying the choice of all of the style decisions are a few basic goals, as listed below. When in doubt about a particular issue, always think about the spirit of these goals. Sometimes these goals will conflict, of course, and in these cases we try to either prioritize the tradeoffs or use experience (either our own or from other groups that have used C++ extensively).
1. Simplicity. When in doubt, keep it simple. Bugs are related mostly to complexity, not code.
2. Clarity. The code should do what it looks like it抯 doing. Other people need to be able to understand your code.
3. Efficiency. Speed and size are important. Using C++ does not imply big and slow. There are plenty of perfectly reasonable ways to make things as fast or faster than the normal C way. Speed and size often trade off, and most people probably err on the side of choosing speed too often. Remember that 20% of the code is responsible for 80% of the time. In most cases, we抮e more concerned about fitting comfortably in less RAM.
4. Appropriateness. Use the language construct that is appropriate for the abstraction or operation you are trying to do. Do not abuse the language. Don抰 use a construct just because it happens to work. Definitely don抰 use a strange construct to amaze and confuse your friends to try to show how smart you are.
5. Natural transition from C to C++. We all used to be C programmers. Others that look at our code are still C programmers (e.g. Word and Excel). When possible, avoid C++ constructs where a C programmer抯 instinct causes a wrong assumption.
6. Catch Errors Early. Having the compiler catch an error is ideal. Having debug code (e.g. Asserts) catch it is the next best thing, etc. Declare things in such as way as 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 have a dramatic impact on build times. This is a constant time sink for everyone. It is often worth rearranging things a little to make incremental builds faster.
8. Consistency. The whole point of having a style guide is that programmers are never totally autonomous, even when the group has strong code ownership. Other people need to read and understand your code. Everyone has to give a little to have a consistent style guide, but everyone gains it back when they read or debug other people抯 code.
2. Classes
C++ classes are a nice way to encapsulate code and data into a single unit, which provides a good paradigm for object-oriented implementations as well other features such as flexible access control, convenient and type-safe polymorphism, and the possibility of code reuse via inheritance.
At the most general, classes are an extension to the built-in typing of C which allows you to define your own types along with the operations on that type. Taken to the extreme, every piece of data in a program could be an instance of a class. However, we will not go nearly this far in Office. We will use classes when there is a good reason to, such as the concept being implemented is inherently object-oriented or polymorphism is required. It has been the experience of many people that programs that use classes for everything evolve into systems that are complex and inefficient. Although this may not be the fault of any particular class, complex class hierarchies can lead to needless complexity, and overly abstracted concepts can easily lead to inefficiency.
In general, we will avoid allocating classes on the stack and passing classes by value, because this is where the use of constructors and destructors gets you into the most trouble. Most classes should be allocated via new, freed by delete, and passed by pointer. In addition, we will never declare a global variable which is an instance of a class that has 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抣l probably use classes only for relatively complex objects that you would normally have in the heap anyway, not simple things like basic data types. Beyond this, it is a judgment call when to use a class. Use one if there is a good reason, but not if a more straightforward solution is just as good.
Summary:
Use classes to encapsulate the implementation of an object-oriented concept.
Use classes to implement polymorphism.
Avoid allocating class instances on the stack and passing them by value. Use new and delete, and pass them by pointer. This implies not using classes for simple data types.
Never declare a global instance of a class that has a constructor.
Not everything is as class. Use them only when you gain something.
2.1 Class vs. Struct
In C++, a struct can also have member functions and operators and everything else that a class can have. In fact, the only difference between a class and a struct is that all members default to public access in a struct but private access in a class. However, we will not use this as the deciding point between using a class vs. a struct. To match the normal intuition, we will use a class if and only if there are member functions included.
Summary:
Use a class instead of a struct if and only if there are 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 will depend on the default only in the case of structs (where we leave all the data implicitly public). For a class, we will declare all members (both data and code) explicitly as public, protected, or private, and group them into sections in 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 class members explicitly as public, protected, or private, in groups in 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 about member vs. local data), and allows the use of the same Hungarian name for, e.g., parameters and members. See the example below.
Data members should normally not be 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 will 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 members. Use inline accessor functions for performance.
2.4 Virtual Functions
Virtual functions are used to allow derived classes to override a method in a base class by providing their own implementation in a way that always causes the most-derived version to be called whenever a method is called through an object pointer, even if that pointer is declared as a pointer to the base class. This is usually done to implement polymorphism, and that抯 when we抣l use them. For example, all COM interface methods are virtual because you are 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 then the data size of each instantiated object will be 4 bytes larger than the combined size of the declared data in order to hold the vtable pointer. After the first virtual function, each additional one only adds another entry to the class vtable, which is static and per-class (nothing per object), so the main concern here is whether a class has any virtual functions at all. In addition to the memory overhead, there is the overhead to indirect a pointer twice before calling the function. This is fairly fast and compact in 32-bit code, but affects speed and size nevertheless. Perhaps the worst part is that virtual functions cannot be inlined, so there will always be a function call, even when the work is trivial.
Because they have overhead, you should not use virtual functions in a class unless you need to. However, make sure you do use them when it makes sense. In particular, if you have a base class which requires a destructor, then the destructor should definitely be virtual to allow derived classes to destruct any added members properly. If the destructor were not virtual, then in a context where polymorphism is being used (so the object pointer is declared as a pointer to the base class), the base class destructor will always get called, even for an object of a derived class that added data members and declared its own destructor in an attempt to free them. The derived class抯 destructor will only get called if the base class destructor is declared virtual. This scenario applies to many other kinds of methods that you will add to your classes. In fact, most of the methods in a base class might be this way if polymorphism is intended. This issues is discussed in more detail in the Inheritance section below.
Note that although virtual functions have a performance penalty over regular member functions, they are often the most efficient way to implement a concept such as polymorphism where the alternative would be large switch statements (not to mention the benefits of the object-oriented encapsulation).
Summary:
Use virtual functions to implement polymorphism.
Virtual functions have overhead, so don抰 use them unless you really should.
A destructor in a base class should always be virtual if polymorphism is intended.
2.5 Constructors
Ah, constructors. Every new C++ programmer抯 nightmare. This is one reason to try to minimize the use of constructors -- C programmers aren抰 used to them and will get confused. Another reason is the infamous performance overhead of calling a function (unless it抯 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 you抮e used to it). Judicious use of destructors (see below) which match the constructors can also help prevent memory leaks and other resource management problems.
Fortunately, the issue is mainly one when classes are declared on the stack or passed by value, both of which we will avoid. Most of our classes should be dynamic memory objects which will be passed around by pointer. In this case, the constructor is essentially just a helper function for the functions that create these dynamic objects. Using a constructor for this purpose is reasonable to ensure a clean and consistent initialization (if you make sure to initialize all data members), but to prevent potential performance problems due to redundant initialization the constructor should not do anything expensive. Simply assigning a constant or a parameter value to each data field is about right. Very simple constructors can be made inline.
Most importantly, a constructor should never be able to fail, because lacking a fancy exception handling mechanism, the caller has no way to handle this in some cases. Any initialization that can fail (e.g. memory allocations) should be put in a separate initialization member function (called, e.g., FInit). When this is the case, it is often useful to encapsulate the creation of an object in a function (a global function or a member 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 do expensive work in a constructor.
If you do make 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 that need to be freed, then the destructor is a convenient place to put this. The normal case for us will be that this is just the central place to free resources for an object that is freed via delete (see below). The trickier use of destructors is for stack-allocated classes, but we抮e going to avoid that by not using classes on the stack.
A destructor should be careful to destroy an object properly regardless of how it was created or used. Furthermore, if you choose to implement a method that frees any resources before the actual destruction, make sure to reset those fields (e.g. set pointers to NULL) so that a destructor will not try to free them twice. It is not necessary for the destructor to reset any fields, though, because the object cannot be used after it is destructed.
Like a constructor, a destructor can never fail. Also, as stated above, a destructor in a base class should always be declared virtual to make polymorphism work.
The destructor for the above example would be 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 are freed before destruction, make sure the fields are reset (e.g. set pointers to NULL) so that a destructor will not try to free them again.
A destructor should never fail.
A destructor in a base class should always be declared virtual if polymorphism might be used.
2.7 New and Delete
The operators new and delete should be used to allocate and free classes (instead of the low-level malloc-like function in your app) so that the constructor and destructor, if any are called properly. We will implement our own global new and delete so that they in turn call our favorite low-level memory manager, so the only difference is really that new does the sizeof automatically and also calls a constructor, and delete calls the destructor.
Note that there must be some mechanism for detecting failed memory allocations. For new, the calling code is responsible for checking. Our memory manager simply returns 0 for a failed allocation, and this will in turn be returned from new (and the constructor will not be called). It is therefore up to the caller of new to check for a 0 return value, as in the example above in the Constructors section.
You should avoid defining any other new and delete (i.e. class-level) operators and stick to the global one to avoid mixed memory models, which complicates things like help optimization and memory leak checking and makes it risky to have the routines that allocate and free a block be different (although this is normally bad structure anyway).
Summary:
Use new and delete to allocate and free classes.
We will implement our own global new and delete in terms of the Office infrastructure memory manager.
Check the return value of new for failure.
Avoid defining any other new and delete operators (use the global ones defined by Office).
2.8 Operators
Ideally, you will never need to define an operator for a class. If you did one, it might be operator=, but don抰 define one unless you really think you want this capability. Next might be operator== and operator!=, but the same applies here, only define them if really needed. We抮e not in the business of providing a general class library that might be used in a certain way, we抮e just implementing code that we actually expect to use ourselves (as explained in a later section, we will not export a real C++ class to anyone). And if you do define these operators, make sure they are efficient so that you are not hiding an expensive operation.
By all means, never define standard operators such as operator+ to do anything other than the standard semantics for built-in objects. Don抰 even push it by doing things like defining, say, operator+ to do a union or concatenation operation. In addition to causing confusion, this hides potentially expensive work behind an innocent-looking operator.
Summary:
Ideally, classes shouldn抰 need to define any operators.
Define 搑easonable” operators such as =, ==, and != only if you really want and use this capability yourself, and if you do they should be super-efficient (ideally inline).
Never define an operator to do anything other than the standard semantics for built-in types.
Never hide expensive work behind an operator. If it抯 not super efficient then make it an explicit method call.
When in doubt, just make a member function to do the work so that the operation is explicit.
2.9 Inheritance
Inheritance is a powerful technique, but it is often misused and can lead to complex class hierarchies that are hard to understand and hard to change. The following sections describe the various uses of inheritance, compare them to other techniques and try to provide rules of thumb about when to use it.
Beyond being appropriate in a particular case, however, just because inheritance can be appropriate does not mean it should be used everywhere. A deep or wide inheritance tree gets hard to understand, hard to browse, and eventually hard to maintain. Keep inheritance limited to a few 搒ilver bullets” where you really win from it.
Summary:
Don抰 use inheritance just because it will work. Use it sparingly and judiciously.
2.9.1 Inheritance of Interface vs. Implementation
Most people think about inheritance as a way to share code. However, one of the most useful ways to use it is simply as a way to ensure working polymorphism by inheriting interface only. The classic example is to have an interface class which is entirely abstract (all methods are pure virtual), and then one or more implementation classes that inherit the interface and implement it in different ways. The OLE COM model is an example of this. A COM interface is expressed in C++ as an abstract base class, and then a separate implementation class inherits from the interface class and implements the interface methods for that object. Here the inheritance is simply a convenient way to ensure that the object speaks the exact interface it is supposed to (has the right methods in the right order in the vtable with the right parameters and the right return types). This is ensured by having each implementation class inherit from the same interface class, which is only declared once in a common header file. Note than when an interface class implements an inherited pure virtual method, it must redeclare it because from a language point of view, it is still considered to 搊verride” the base method. For example:
// The interface base class provides interface only
class FooInterface
{
public:
virtual void DoThis() = 0; // pure virtual
virtual void DoThat(int i) = 0; // pure virtual
};
// The implementation class implements the FooInterface interface
class FooImplementation: public FooInterface
{
public:
virtual void DoThis();
virtual void DoThat();
}
void FooImplementation::DoThis()
{
...
}
...
The above example shows the case where the entire base class is interface only. However, inheritance of interface only can also happen at the level of an individual member function in a base class which also includes some implementation. This is the case when any member function is declared pure virtual. An example of this is shown below with the DrawObj::Draw method.
The above example does not use inheritance to share code. However, inheritance can also be used for this, and this is done by providing implementations of methods in a base class that inherited classes can use. There are two interesting cases here. If the base class defines an implementation of a method which can either be used or overridden, then the base method is defining an interface with a default implementation. In this case, the method should be defined as virtual so that any class which overrides the method will get the right result when polymorphism is used. Alternately, if the base class method provides an implementation of a method which is not meant to be overridden (because it does a standard action on data which is private to the base class), then the base method is defining an interface and a required implementation. In this case, the method should not be declared virtual. The converse of this is that when inheriting from a class, do not override any non-virtual functions because this could lead to maintenance problems when the base class is changed.
In general, the two cases of inheritance of implementation outlined above as well as the case of inheritance of interface only can all be combined in a single class by having different methods do different things. The key is to decide, for each method, whether the goal of the base method is to provide interface only, interface plus default implementation, or interface plus required implementation. For example:
// A base class for drawing objects
class DrawObj
{
public:
virtual void Draw() = 0; // interface only
virtual BOOL FHitTest(POINT pt); // default implementation
void GetBounds(RECT *pr); // required implementation
private:
Rect m_rBounds; // bounding rectangle
};
BOOL DrawObj::FHitTest()
{
return PtInRect(pt, m_rBounds);
}
void DrawObj::GetBounds(RECT *pr)
{
*pr = m_rBounds;
}
In this example, the Draw method is pure virtual because it is only specifying an interface for polymorphic use. Any derived class that can be instantiated must define the Draw method. The FHitTest method is defining interface (for polymorphism) as well as a default implementation. Any derived classes that don't need non-rectangular hit-testing can just use the default implementation (no code or declaration required), but other classes can simply override this method and do special hit-testing. The GetBounds method is an example of a required implementation. The base class requires that "bounds" be defined in the same way for all objects, and it doesn't make sense for anyone to change it. In this case, the member does not need to be virtual (and should not be for clarity and efficiency) because the base class implementation is always used.
Summary:
Inheritance of interface can be used for ensuring a consistent (e.g. polymorphic) interface.
An implementation class can inherit its interface from an interface class where the interface class has only pure virtual methods.
When using inheritance of implementation to share code in a base class,
Use pure virtual functions to provide interface only.
Use virtual functions to provide interface and a default implementation.
Use non-virtual functions to provide interface and a required implementation.
2.9.2 Inheritance vs. Containment
The most common misuse of inheritance is to view inheritance as a way to share code among 搒imilar” objects and to use it in a context where there is no real 搃s a” relationship. There are several ways to share code, and the simpler technique of containment and delegation (one class contains another and delegates the relevant functionality to it), which we抮e all used to from traditional structured programming, works fine in most cases. In this case, the relationship is described as 揾as a”.
The primary reason to use inheritance instead of containment is to achieve polymorphism (in conjunction with the use of virtual functions). The easiest way to test for an 搃s a” relationship is to think whether polymorphism is what is desired. If so, then inheritance could be appropriate (assuming any other practical concerns are met). Another way to test 搃s a” vs. 揾as a” is to ask yourself if it could make sense to have more than one of the base class in the derived class. If so, then 揾as a” (containment) is the right model. For example, if you were implementing a scrolling window and you already have a scrollbar class, you would notice that a window could have two scrollbars (horizontal and vertical) even if you weren抰 planning on that feature in the first version, so a window should contain (揾as”) a scrollbar, not inherit from (搃s”) one.
Even when you do decide to use inheritance from another class, it is often the case that you should split the original class into a base class and a derived class and inherit only from the base class. This allows you to split off only the stuff that is really shared. For example, say you had a Rectangle drawing object, and now you want an 揙val” object. You convince yourself that polymorphism is desired (e.g. drawing and hit-testing code in the caller wants to treat all objects the same), and that an Oval would never want two Rectangles. Now you might decide to have the Oval inherit from the Rectangle, but probably what you really want is to split the Rectangle class into a base DrawingObject class and a separated derived Rectangle class, and then Oval would inherit from DrawingObject, not Rectangle. This allows later changes to the Rectangle object that are specific only to it, even if this isn抰 needed now. As in the example from the previous section, the DrawingObject base class will probably have a combination of pure virtual methods to enforce the polymorphic interface, virtual methods to provide a standard interface as well as a default implementation for all 搒imple objects”, and non-virtual methods to provide required implementation of stuff that is common to all objects and assumed to be constant in the common code.
Note that containment forces you to use the contained object抯 public interface, whereas inheritance allows use of protected members also. This is another way of saying that containment is more encapsulated than inheritance. In fact, it is often said that inheritance breaks encapsulation because it can create dependencies on the implementation of the base class. This is particularly true in the case of overridden functions, where a change to the base class might not have the right effect on all derived classes.
Summary:
Be careful with inheritance vs. containment. When in doubt, use containment.
Inheritance is an 搃s a” relationship, whereas containment is a 揾as a” relationship.
Test for 搃s a” by seeing if polymorphism is desired or makes sense.
Test for 揾as a” by asking yourself if one class could ever use more than one of the other class.
2.9.3 Multiple Inheritance
We will avoid multiple inheritance altogether. Multiple inheritance has a number of problems including resolution of name conflicts, efficiency concerns of some operations (functionality is hidden from you), maintenance problems, and general confusion about what the heck is going on.
If you are building a large and complex inheritance hierarchy (to be avoided as noted above), you might find yourself wanting multiple inheritance to share code from two different places. In the case of literally sharing code from two different places, this is the most dangerous form of multiple inheritance because it leads to the trickiest dependencies. There are other forms of multiple inheritance, though. The safest is multiple inheritance of only interfaces (no code from any base class), but even this has problems with things like name conflicts. So, we will avoid it altogether.
Every time you think you need multiple inheritance, you should consider that maybe you are over-using inheritance and you should switch to containment in some cases. Inheritance is a silver bullet that you have only one of. Once you抳e used it for a given class, you need to use containment to get anything else. Note that you can use containment as much as you want within a given class with no problems.
Summary:
Don抰 use multiple inheritance.
Given only single inheritance, inheritance is a 搒ilver bullet” which you have only one of, so use it sparingly and judiciously.
3. Other C++ Features
The following sections comment on various new features of C++ that aren抰 directly related to classes.
3.1 Constants and Enumerations
C++ adds the concept of true constants to C. In C, you had the choice of using a #define or declaring a "const" global variable. However, the #define will not be type safe, and the const variable takes up real memory and isn't optimized. For example:
// C alternatives:
#define dxMin 0 // not type safe
const DX dxMin = 0; // just a real global variable
In C++, the const syntax declares a real constant of the specified type that the compiler will type-check, and then substitute the actual value in-line and optimize (fold constants, etc). As a bonus, the debugger will even know about this symbol. For example,
// C++ solution:
const DX dxMin = 0; // type safe, optimized, and debug symbol
So true C++ constants are preferred to the traditional C++ #define. Note that they cannot be used in shared C/C++ header files, though, because a C compiler will just allocate memory for them.
C++ also makes the existing C concept of an enum type safe. An enum in C++ defines a type and declares constants of that type. You can then use that type as, say, a parameter to a function, and the compiler can then enforce that you pass one of the symbols defined in the enumeration (or you can type cast to get around this if you need to). An enum can also be made local to a class so that its scope is limited. For example,
class Foo
{
public:
enum GMODE { gmNo = 0, gmYes = 1, gmMaybe = -1 };
void InsertGNode(GMODE gm);
};
Summary:
Use const or enum instead of #define for constants that are only used in C++.
3.2 References
C++ adds the ability to express references to objects, and the primary use of them is to pass classes as parameters without the overhead of the copy constructor being called. This is a worthy goal, but a more straightforward method to do this is to just to pass classes by pointer, which is what we抮e all used to from C. For someone used to C, seeing something being passed by reference looks like it抯 being passed by value, so you might wonder if the constructor is being called, or whatever. Furthermore, when using a reference, the illusion is that you have a local copy of the object that you can reference cheaply, but in fact you just have a pointer to the object (this is how the compiler does it), and every access is an indirection. We should just make this indirection explicit by actually using pointers. Typing “*” or “->“ instead of “.” is not a big deal, and it makes it more clear what is going on. The one real advantage of references over pointers is that they are guaranteed to be initialized (they cannot be NULL or point to garbage). But this advantage is not worth the above problems for us.
Also note that when you do pass objects by pointer, use const to mark formal parameters that are read-only (see the "Const" section below). This is related to references because some C++ programmers will use the convention that read-only objects are passed by references and other objects are passed by pointer (to make this safe you still need to the declare the reference const because C++ will let you change a parameter through a reference). This is a reasonable convention, but it still has the problem of looking foreign and confusing to programmers with a C background. So, we will use const to get the safety but pass every object by pointer.
There are other more exotic uses of references, such as being able to return an lvalue from an operator function, and sometimes this is necessary if you've defined such an operator. But since we don抰 plan to use operators much if at all (because we won抰 use stack-based classes), we should be able to avoid references in most cases.
Summary:
Avoid references. Pass objects that are larger than an 搃nt” by pointer.
3.3 Const Parameters and Functions
As mentioned above, you should use const to mark formal parameters that are read-only. This allows the compiler to check that you actually obey this, serves as a form of documentation to users of your function, and also allows the compiler to produce better code in some cases. For example:
/* Copy the contents of 'fooSrc' into 'fooDst'. */
void CopyFoo(const FOO *fooSrc, FOO *fooDst);
You can also declare non-pointer formal parameters as const (as well as the actual pointer portion of a pointer parameter, rather than what it points at, in which case the word "const" may appear twice for that parameter), but this is not as much of a win and it can make the prototype harder to read, so it's optional. This just makes sure that you don't reuse the parameter itself as a local variable and change its value. Of course, sometimes a function will do this as a way of avoiding declaring and setting up a local variable, so in this case you can't use const (not that this is not great programming style, but we're not going to disallow it). On the other hand, if you don't change the value of the parameter within the function, declaring it as const may allow the compiler to generate better code. Note that doing this does not give any useful documentation to the caller, though. For example:
/* Copy 'cb' bytes of the contents of 'fooSrc' into 'fooDst'.
In addition to not changing what 'fooSrc' points at, my implementation
promises not to change the values of any of the local parameters
within the function (like you care...). */
void CopyFooCb(const FOO *const fooSrc, FOO *const fooDst, const int cb);
In addition to declaring parameters const, you can also declare a member function const to indicate that the function does not modify the object. Again, this allows compiler checks and possible optimization as well as a form of documentation. For example:
class Counter
{
public:
int CItems() const { return m_cItems; }
void SetCItems(int cItems) { m_cItems = cItems; }
private:
int m_cItems;
};
Summary:
Use const to mark read-only pointer parameters (what the pointer points at, not the pointer itself).
Use const to mark member functions that don't change the object
Use const to mark parameters themselves only if you care about the possible performance gains in the implementation.
3.4 Default Arguments
Having default arguments seems like a cool feature. It seems like a way to add a parameter which only some calls to a function will need to pass, so that the simple cases will be kept simple. Well unfortunately, there is no efficiency gain here, and instead the compiler is just hiding work from you. If you have a function with one required argument and four optional arguments, every call to this function will push all five arguments, so you are getting the code size and time hit in every case. Furthermore, you can抰 even use default arguments just to try something new without bothering with the old calls because you still have to find all the old calls in order to rebuild those files (if you do an incremental build of just one use after adding a default argument, the other calls will screw up). Finally, default arguments can be easily confused with overloading (which we抣l also avoid).
There are cases, however, where a certain parameter is totally irrelevant in a call (because, for example, the value of another parameter tells you all you need to know). Note that this is somewhat different from a default argument because there is no real default value for this parameter. In these cases, it is nice to have the untyped constant "NA" , #defined to be 0, to stand for "Not applicable" which can be passed to indicate this for any actual parameter. This is better than passing, say, NULL for a pointer or FALSE for a Boolean because it makes it clear that the value is not important at all. For example:
#define NA 0 // universal "not applicable" parameter value
/* If fShow then show the object, else hide it. If showing then
redraw it only if fRedraw.
void Show(BOOL fShow, BOOL fRedraw);
void Ack()
{
Show(TRUE, TRUE);
...
Show(FALSE, NA);
}
Summary:
Don't use default arguments.
Use "NA" (#defined to be 0) for "not applicable" parameters in a call.
3.5 Function Overloading
Overloading functions is just a lazy naming scheme. It seems like a form of polymorphism, but it shouldn抰 be confused with real polymorphism because all the decisions must be made staticly at compile time. It抯 just a way to keep 搒imple” function names and reuse them for different cases. Such a lazy naming scheme just causes more confusion than it抯 worth (trying to determine which function is relevant, changing the wrong one by accident, etc.) and can also interfere with proper use of Hungarian in some cases. Finally, the combination of function overloading and type coercion can be quite confusing indeed.
Summary:
Don抰 overload functions
3.6 Operator Overloading
The main use of operators is in classes. This is discussed in a previous section. Operators can also be overloaded at global scope. For example, you can define what operator+ should do when it finds a Foo on the left side and a Bar on the right side. Unlike the use within a class, this allows control over the left-hand side operand. Anyway, all the same problems apply and more (due to the larger scope). Functionality is hidden (a possible efficiency problem) and confusion can result, so we will avoid this.
Summary:
Don抰 overload operators, especially at global scope.
4. Common C/C++ Issues
The following sections comment of features of regular C that we also try to maintain a consistent use of.
4.1 #ifdefs
First and foremost, everyone should try really hard to minimize the use of #ifdefs. Programs with lots of #ifdefs are really hard to read. It is often possible to either invent the right abstraction or to isolate the #ifdefs to small places in header files, or make the right definitions in target-specific headers so as to make #ifdefs unnecessary in the main code. The main argument for #ifdefs (over, say, a regular 搃f”) is to minimize the code size. However, everyone should be aware that the optimizer is perfectly capable of simplifying statements that evaluate to a constant. For example,
// Wrong:
#ifdef MAC
if (x == 3 || Foo() || FSomeMacMode())
#else
if (x == 3 || Foo())
#endif
// Right:
// In a header file for each non-Mac platform, there is
#define FSomeMacMode() FALSE
// Then the using code can just be
if (x == 3 || Foo() || FSomeMacMode())
In this example, the compiler is perfectly capable of eliminating what amounts to (搢| FALSE”) at compile-time. Furthermore, if the entire "if" were to always evaluate to FALSE at compile time, then the optimizer will also remove all of the code inside the "if" and remove the test.
If you must use an #ifdef, we prefer to use #if instead because it's shorter and allows logical operations, as in:
int Foo()
{
int x = 3
#if MAC && !DEBUG
x = 0;
#endif
return x;
}
Note that we will still leave flags such as DEBUG undefined when false, but the compiler does the right thing here (treats it the same as being defined to be 0). Leaving these flags undefined means that #ifdef will also work, in case this is used by accident anywhere.
Also, as this example shows, #ifs should be properly indented so that they read easily when nested. Yes, this works; C Compilers have accepted this for years.
Aside from the standard identifiers defined by our build process (e.g. DEBUG, MAC), we will also use the identifiers UNUSED and LATER, which are never defined, to mark code which is currently unused but kept in for some reason, and code which cannot be activated yet but will eventually, respectively.
Summary:
Minimize #ifdefs by defining good abstractions, partitioning files better, or defining appropriate constants or macros in header files for the compiler to figure out.
Prefer #if over #ifdef.
Indent an #if with the code so that it reads better when nested within others.
Use #if UNUSED for code that is on longer used but kept in for some reason.
Use #if LATER for code that cannot be used yet but will eventually.
4.2 Global Variables
Global variables are usually bad programming style. More specifically, they are often trouble when used to cache 揷urrent” state because it is so easy to get them out of sync. Everybody knows this from their past experience, so there抯 no point in going into gory detail. In addition to these problems, globals make things such as multiple uses of a DLL in the same process, reentrancy, and multi-threading very hard to achieve, and we need to worry about all of these in Office.
Due to the DLL/process/thread problem (all instances of the same DLL in a process as well as all threads share the same copy of the globals), most things that you might normally of as global should go in an instance data structure. In Office, this structure is the MSOINST struct, which is allocated and returned for each caller of MsoFInitOffice.
When you do use a global (e.g. for data which is truly shared among all uses in a given process), use the Hungarian 搗” prefix before the type tag to make the global clear.
Summary:
Minimize global variables. Most per-client read/write storage should go in the MSOINST structure.
Use the Hungarian 搗” prefix at the beginning of global variables.
4.3 Macros and Inline Functions
In C++, what would be a functional or procedural macro in C is usually better expressed as an inline function because this makes it type safe and avoids problems with multiple evaluation of macro parameters. For example:
file://Wrong
#define XOutset(x) ((x) + 1)
#define XMin(x1, x2) ((x1) < (x2) ? (x1) : (x2))
file://Right
inline XY XOutset(XY x) { return x + 1; }
inline XY XMin(XY x1, XY x2) { return x1 < x2 ? x1 : x2; }
In addition, inline functions can be limited to the scope of a class if appropriate in order to make them more specific.
Note that #define must still be used in public header files that are exported to C clients. Actually, some C compilers do implement inline functions (via __inline), though, so this may be possible in C also if all of your relevant compilers support it.
When you do use #define to write functional macros with arguments, be sure to remember to enclose each instance of an argument in parentheses, and also the entire expression if necessary, to avoid precedence problems.
Summary:
Use inline functions instead of #define macros when possible.
In functional macros, enclose all argument instances in parentheses as well as the entire expression if necessary.
4.4 Optimization
Always know what code the compiler generates for your favorite constructs, and what things typically get optimized. Trying to hand-optimize your code can cause bugs and can also actually make the generated code worse. For example, introducing temporary variables may cause something that would have been pre-computed and cached in a register to be moved into a stack variable. It is worth it for everyone to play around with sample constructs, compile with /Fc, and look at the .cod file to see what gets generated. Finally, remember that the expanded code you see when debugging is not the optimized code; it is totally unoptimized for debugging purposes (e.g. to make it line up exactly with the source statements).
Another issue to be aware of is that in order to get maximum benefit from our optimizer, we want to enable lots of different kinds of optimization. Some coding constructs can confuse the optimizer and cause bugs (for example, depending on subtle aliasing behavior). The rule of thumb here is that the more straightforward your code is, the better the optimizer will be able to improve it and the less chance of an optimization bug. This is always good practice anyway, because it makes your code easier for people too.
Summary: