Office Source Code Style Guidedave Parker, 6/30/95
AbstractThis 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.Contents1. GENERAL GOALS 32. CLASSES 32.1 CLASS VS. STRUCT 42.2 PUBLIC, PRIVATE, AND PROTECTED MEMBERS 42.3 DATA MEMBERS 42.4 VIRTUAL FUNCTIONS 52.5 CONSTRUCTORS 52.6 dESTRUCTORS 62.7 NEW AND DELETE 72.8 OPERATORS 72.9 INHERITANCE 82.9.1 Inheritance of Interface vs. Implementation 82.9.2 Inheritance vs 102.9.3 Multiple Inheritance 113. Other C Features 113.1 Constants and Enumerations 123.2 References 123.3 Const Parameters and Functions 133.4 Default Arguments 133.5 Function Overloading 143.6 Operator OverLoading 144. Common C / C
ISSUES 144.1 #IFDEFS 144.2 GLOBAL VARIABLES 154.3 MACROS AND INLINE FUNCTIONS 164.4 OPTIMIZATION 164.5 WARNINGS 174.6 PRIVATE DATA AND FUNCTIONS 174.7 TYPEDEFS 174.8 BASIC DATA TYPES 174.9 POINTERS 184.10 SWITCH STATEMENTS 194.11 ASSERTS 194.12 ERRORS AND EXCEPTIONS 195. FORMATTING CONVENTIONS 205.1 NAMING CONVENTIONS 205.2 FUNCTION PROTOTYPES 215.3 VARIABLE DECLARATIONS 225.4 CLASS DECLARATIONS 225.5 COMMENTS 235.5.1 File Headers and Section Separators 235.5.2 Function Headers 245.5.3 In-Code Comments 255.5.4 Attention Markers 255.6 MISC. FORMATTING CONVENTIONS 265.7 SOURCE FILE ORGANIZATION 275.7.1 Public Interface Files 275.7.2 Private Interface Files 285.7.3 Implementation Files 285.7.4 Base Filenames 296. INTERFACES TO DLLS 296.1 C FUNCTIONS AND GLOBAL VARIABLES 296.2 COMMON C / C PUBLIC HEADER FILES 296.3 LIGHTWEIGHT COM OBJECTS AND ISIMPLEUNKNOWN 307. APPENDIX A: BASIC HUNGARIAN REFERENCE 337.1 MAKING HUNGARIAN NAMES 337.2 Standard Base Tags 337.3 Standard Prefixes 347.4 Standar D Qualifier 35
1. General GoalsC is a complex language that provides many ways to do things, and going prop 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 Yun much rather err on the side of just dealing with C, which we Zhen 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 Zha 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 Zhen 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 Yang use a construct just because it happens to work. Definitely don Yang 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 Programmmers. Others That Look Atur Code Are Still C Programmers (EG Word and Excel). When Possible, Avoid C
constructs where a C programmer Zha instinct causes a wrong assumption.6. Catch Errors Early. Having the compiler catch an error is ideal. Having debug code (eg 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. Eveyone Has To Give a little to have a consistent style guide, but everyone gains it stockime 抯 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 via code reuse 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 ov Erly Abstract Concepts Can Easily Lead To INEfficient.
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 Yun 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. StructIn 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 Mess. However, We will not use this as the deciding point bagween 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 membersAs 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 Theim INTO Sections in That Order. for example: Class foo {public: foo (); ~ foo (); void hey; 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 MembersData 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, eg, 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 {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 FunctionsVirtual 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 Zha when we Yun 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 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 Vi rtual 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 .................... ..
Perhaps the worst part is that virtual functions can not 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 Zha 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. this issues is discussed in more detail in the inheritance section.
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 Yang use them unless you really should. A destructor in a base class should always be virtual if polymorphism is intended.2.5 ConstructorsAh, constructors Every new C . Zha programmer nightmare This is one reason to try to minimize the use of constructors -. C programmers aren Yang used to them and will get confused Another reason is the infamous performance overhead of calling a function (unless it Zha inline) and doing work. At Possibly Unexpected and / or Redundant Times.however, Using Constructionors Can Eliminate The Dangers of Uninitialized Data And Can Also Made The Code Simpler To Read D 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 (EG Memory Allocations) SHOULD BE PUT IN A separate initialization member function (called, eg, 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; returnaf
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 DestructorsIf 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 Zhen 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 (eg set po inters 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 can not 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 (eg 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 DeleteThe 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 ma nager 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 (ie 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 (Althought ". 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 OperatorsIdeally, you will never need to define an operator for a class. If you did one, it might be operator =, but don Yang 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 Zhen e not in the business of providing a general class library that might be used in a certain way, we Zhen 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 Yang 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 shouth 抰 Need to define any operatrs. 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 Zha 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 InheritanceInheritance 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 i nheritance limited to a few whip ilver bullets "where you really win from it.Summary: Don Yang use inheritance just because it will work Use it sparingly and judiciously.2.9.1 Inheritance of Interface vs. ImplementationMost 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 the Omber 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 pluck verride "the base method For example:. // The interface base class provides interface onlyclass FooInterface {public: virtual Void Dothis () = 0; // Pure Virtual Virtual Void Dothat (INT I) = 0; // Pure Virtual}; // The Implementation Class Implements The Foointerface InterfaceClass FooImPletionation: 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. The convers, do not overrides, do not override any n on-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 ObjectsClass Drawobj {public: Virtual Void Draw () = 0; // Interface Only Virtual Bool Fhittest (Point Pt); // Default Implementation Void getBounds (Rect * PR);
// Required ImplementationPrivate: Required ImplementationPrivate: 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 do not 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, The Member Does Not Need To Be Virtual (AND SHOULD NOT BE for Clarity) Because The Base Class Implementation is always used.summary:
Inheritance of interface can be used for ensuring a consistent (eg 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. ContainmentThe most common misuse of inheritance is to view inheritance as a way to share code among whip imilar "objects and to use it in a context where there is no real Zong sa" 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 Cas e, the relationship is described as Useful 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 Zong sa" 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 搃 SA "VS.
揾 As a "is to ask yourself if it could make sensable to have more Than One of the base class in the derived class. If So, TEN 揾 As a (containment) is the right model. For example, if you want implemementing 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 Yang planning on that feature in the first version, so a window should contain (Useful as ") a scrollbar, not inherit from (Zong 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 Bian val "object. you convince yourself that polymorphism is desired (eg 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 Yang 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 Zha 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 Zong sa "relationship, whereas containment is a Useful as. a "rellationship. Test for 搃 sa" by seeing if polymorphism is desired or makessse. Test for 揾 as a "by asking yourself if one of the class cost sale more more one of the other clas .2.9.3 Multiple InheritanceWe 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 Ni 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 Yang use multiple inheritance. Given only single inheritance, inheritance is a whip ilver bullet "which you have only one of, so use it sparingly and judiciously.3. Other C FeaturesThe following sections comment on various new features of C that aren Yang directly related to classes.3.1 Constants and EnumerationsC adds the concept of true constants to C. In C, you had the choice of using a #d efine or declaring a "const" global variable However, the #define will not be type safe, and the const variable takes up real memory and is not optimized For example:.. // C alternatives: #define dxMin 0 // not type safeconst DX dxMin = 0; // just a real global variableIn 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 can not 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 get 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 ReferencesC 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 Zhen e all used to from C. For someone used to C, seeing something being passed by reference looks like it Zha 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 CLEA r what is going on. The one real advantage of references over pointers is that they are guaranteed to be initialized (they can not 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 Yang plan to use operators much if at all (because we won Yang use stack-based classes), we should be able to avoid references in most cases.Summary:. Avoid references Pass objects that are larger than an Zong nt "by pointer.3.3 Const Parameters and FunctionsAs 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 Case. 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 do not reuse the parameter itself as a local variable and change its value. of course, sometimes a function will do THIS A WAY OF AVOIDING DECLARING AND SETING UPA 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 dispialow it). on The Other Hand, . if you do not 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 change ng 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 do not change the object Use const to mark parameters themselves only if you care about the possible performance gains in the implementation.3.4 default ArgumentsHaving 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 Yang 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 th ose 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 Yun 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 THSE PARAMES, IT IS NICE TO HAVE The Unred 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 the show the object, else hide it. if silk, the redraw (Bool Fshow, Bool FredRaw);
Void Ack () {show (True, True); ... show (false, na);
Summary: Do not use default arguments. Use "NA" (#defined to be 0) for "not applicable" parameters in a call.3.5 Function OverloadingOverloading functions is just a lazy naming scheme It seems like a form of polymorphism. , but it shouldn Yang be confused with real polymorphism because all the decisions must be made staticly at compile time. It Zha just a way to keep whip imple "function names and reuse them for different cases. Such a lazy naming scheme just causes more confusion than it Zha 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 Yang overload functions3.6 operator OverloadingThe 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 i... t 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) . FunctionAry 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
IssuesThe following sections comment of features of regular C that we also try to maintain a consistent use of.4.1 #ifdefsFirst 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 Zong 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 Macif (x == 3 || foo () || fsomemacmode ()) # elseif (x == 3 | | Foo () # Endif // Right: // in a Header File for Each Non-Mac Platform, There IS # define fsomemacmode () False
// THE Using code can Just Be if (x == 3 || foo () || fsomemacmode ())
In this example, the compiler is perfectly capable of eliminating what amounts to (shake | 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; #ndif returnix;
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 (eg 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 can not 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 can not be used yet but will eventually.4.2 Global VariablesGlobal variables are usually bad programming style. More specifically, they are often trouble when used to cache Cha urrent "state because it is so easy to get them out of sync. Everybody knows this from their past experience, so there Zha 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 proce ss 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 (eg for data which is truly shared among all uses in a given process), use the Hungarian trace "prefix before the type tag to make the global clear.Summary:. Minimize global variables Most Per-Client Read / Write Storage Structure. Use the hungarian "prefix at the beginning of global variables.4.3 macros and inline functionsin 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: // Rightinline XY XoutSet (xy x) {return x 1;} inline XY XMIN (XY X1, XY X2) {RETURN X1 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 OptimizationAlways 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 generate d 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 seehen debugging is not the optimized code;