Академический Документы
Профессиональный Документы
Культура Документы
Preface
This document provides conventions for writing C++ code. Coding conventions described here should be adopted when creating, changing and refactoring the C++ code for Eon Technologies. Conventions are separated to two groups:
There are only handful basic requirements, which you are required to follow. They were carefully chosen not to affect the productivity. The objective for the requirements is to ensure basic unique shape of all the C++ code, produced by or for Software System Solutions. As a foil to requirements, recommendations are not "de iure" standard, but selected best current practices. You are free to make your own decisions about them. You are free to ignore them where they would impose limitations and/or any kind of penalties to your efficiency. The objective of recommendations is to promote coding styles, based on experience, idioms and patterns that could be described preventive and lead to concise, solid and reliable code. Coding conventions are a living language. As programming knowledge will grow new idioms will find their way into this document. Therefore it is by no means thought of as complete. If you find out new facts or have an idea about how to improve it, you are encouraged to come forward with your proposals for improvements.
General Guidelines chapter discusses latest development in C++ and MS community and about evolution of C++ programming in general. Choice of Names chapter prescribes naming for variables, methods, classes, enumerations, structures, file naming conventions and conventions about naming other elements of C++ source. Shape of Code chapter prescribes spacing and line breaking in your code. Comments
Redundant mechanisms
MFC and Standard C++ with STL both introduce their own solutions for some commonly used abstractions in programming:
You are encouraged to use Standard C++, including new powerful features: new casts, namespaces, templates and the STL...
4 Coding Conventions for C++
Source files
In general a class should be stored in two files. In header file (with .h extension), which contains declaration of the class and in implementation file (with .cpp extension). Chosen filenames can follow long filename convention. You are allowed to use basic letters of English alphabet in names of files. You are also allowed to use numbers, underline symbol (_) and ONE AND ONLY ONE dot symbol, which must have a function of dividing the name from the extension. Use of spaces is not allowed in file names. For line breaking in source files CR+LF combination must be used.
Portability issues
Using prescribed naming convention (including VC++ conventions) will make your files portable between various platforms (with respect to line breaking characters).
When programming under Windows you MUST use VC++ convention. It prescribes capitalized words in name.
Samples:
UserAuthenticationSystem.cpp User Authentication System.cpp user_authentication_system.h UserAuthenticationSystem.h CommonObjectBroker.cpp ObjectValidator.cpp objectValidator.h // OK, MFC // INVALID // OK, NOT USING MFC // OK, MFC // OK, MFC // OK // OK, NOT USING MFC
It is warmly recommended that you use VC++ file naming conventions on all platforms for their generality. By following this recommendation problems will be avoided when unzipping your files on another platform.
In non-Windows sources classes, which are derived from std. library classes, can use same conventions as the library.
Samples:
class QueueException : public exception; // OK class queue_exception : public exception; // OK, using std. library convention class MyList : public list<MyObj>; // OK
Exception are interface classes (also applies to pure virtual classes!) which are prefixed with capital letter I, since this is commonly accepted standard.
Samples:
class IConnectionPoint class IUserManager class IMessageQueue
Samples:
class CFile; struct SConnectionPort { unsigned data:8; unsigned parity:1; unsigned control:2; }; typedef SConnectionPort TConnector; TConnector conr; // OK
Sample:
enum ETaskType { copyFile, installFile};
Variables
Common variables
The name of the variable should describe the intention (purpose) of variable with respect to name length. Best names are from 8 to 16 characters long and descriptive. If variable name is composed of many words then all words in variable must be capitalized. In addition a name must be prefixed with it's type (Hungarian notation)! Sample:
int iLinesPerPage;
A list of prefixes for standard C++ types and common Windows types is given in Appendix A.
If variable is of type custom class then the prefix is optional. It can be composed as two to three letter abbreviation, it can be generic a or obj prefix or no prefix. Samples:
CConnection cnPersonDatabase; CDynamicAllocator DynAllocr; CActiveFolder objFolder; CPerson aPerson;
Global variables
Global variables must be prefixed with g_ or gl_.
Samples:
CPermanentConnection gl_cnSettingsDatabase; CappSingleton g_objApplication;
Samples:
// Windows class class CMyClass { int m_iMemberVariable; char m_cMemberVariable; }
In non Windows source variable can be prefixed or (recommended, due to compiler's usage of prefix) post fixed with an underline.
Sample:
// Generic class class GenericClass { int iMemberVar_; char cMemberVar_; }
Prefix or postfix notation are both correct for generic classes. It is good to decide for one in the scope of same file.
Arrays
Array is a special kind of variable. It follows same naming convention. The name of array must use plural instead of singular.
Samples:
CList<Person&, Person&>arrPersons; vector<CCar>vctCars; // Cars, with optional type prefix int iSquares[]={1,2,4,8,16,32,64,128};
Instead of macros you should use inline functions and instead of preprocessor constants you should use namespace constants, class constants or global constants. Words in preprocessor symbols can be divided by underline symbol. You may prefix constants with _, to differentiate them from preprocessor macros.
Samples:
#define MAX(a,b) (a > b ? a : b) #define _PI 3.14 #define _MAGIC 1234
Sample:
// MFC directives #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif // include test #if !defined(_OBJBROKER_H) #define _OBJBROKER_H ... body of objbroker.h file ... #endif
First word in method should be a verb explaining what the method does. Further text can contains nouns on which method operates.
Samples:
GetUserInfo(LONG lUserObjectID); DeleteAll(void);
A special kind of method is method returning Boolean value. This method can start with prefix is.
Samples:
IsValidZipCode(...); IsEmpty(...);
Sample:
class CItem { private: CString m_sDisplayName; public: CString DisplayName(); // get method void DisplayName(CString sDisplayName); // set method }
The second way is to add noun Get and Set in front of the property name.
Sample:
class CItem { private: CString m_sDisplayName; public: CString GetDisplayName(); // get method void SetDisplayName(CString sDisplayName); // set method }
Private classes as class members are exception to the rule. They should not be exposed trough get/set properties since that would violate encapsulation. Instead delegation should be used as access technique.
Sample:
class CItem { private: CSubitem m_objSubitem; public: DoSomething() { m_objSubitem.DelegatedDoSomething() }; }
Shape of Code
12 Coding Conventions for C++
Samples:
if ( a == b ) DoSomething(); b = c; d << f;
Samples:
a++; // NOT a ++ !!! b = -c; if ( !b ) DoSomethingElse();
Sample:
if ( m_nWatchesNumber < 0 ) m_hResultError = ERROR_EMPTY_WATCHER; else { m_hResultError = ERROR_NO_NOTIFIER; m_IDs.RemoveAll(); for ( int nCount = 0; nCount < m_nWatchesNumber; nCount++ ) { if ( m_pWatchNotifier[nCount].GetRegKey() == 1 ) { m_IDs.Add(m_pWatchNotifier[nCount].GetWatchID()); m_hResultError = ERROR_SUCCESS; bFilled = TRUE; } else { m_pWatchNotifier[nCount].SetRegKey(0); m_bHasZero = TRUE; } } }
Sample:
class MyClass { public: MyClass(void); // ctor virtual ~MyClass(); // dtor protected: int _iSomeMember; // attribute virtual void ExecuteOperation()=0; // overridable private: bool _bPrivate; // attribute void DoSomething() { // implementation Something(); } }
Templates for documentation are fully customisable, Documentation can be created during the build process by adding several commands to the makefile or adding post-build steps to IDE (for example a batch file running autoduck and copying documentation to appropriate directory).
By using Autoduck with it's search and indexing mechanisms there is no need for additional code documentation beyond detailed design document since all documentation is created directly from source code by every build. Proposed procedure for commenting the code is: 1. Write the module. 2. Test it. 3. And only after you have a candidate for review (i.e. 1.0) comment it. This way no excessive changes will be needed to comments as you change the code during development.
All interfaces to the outside world!, Most classes, especially classes that will interact with other people's code, by documenting in detail: class purpose, properties and methods, All functions that are visible beyond the scope of a file in which they are declared, Data structures, Special algorithms used in class (at the top of file), All files (by attaching special comment at the top), common code (hint: control statements)
Every source file (every header file and every implementation file) must also have an introduction comment explaining basic information about the file.
Sample:
DoSomething(); // now do something
If comment can't be written in one line it should be written in many lines and idented.
Sample:
DoSomething(); // comment for single line of code stretching over // multiple lines
In case line or comment is too long to be in the same line then comment should be written at the top of code indented with it. Sample:
for (int i = 1; i < 100; i++) // comment for code following DoSomethingVeeeeeeeeeeeeeeeeeerrrrrrryyyyyyyyyyLooooooooonnnnnnng();
Some revision control systems add changes to module automatically by providing tags such as $Log:$ in the head of file. However since revision control system has not been chosen yet versioning has to be done manually.
Sample:
////////////////////////////////////////////////////////////////////// // // // // // // // // // // // // // // // // // ////////////////////////////////////////////////////////////////////// NOTE: / Feb the 16th Stilian (stil@eontechnologies.com) 1.0 Tested and released. Copyright 2000 Eon Technologies, All Rights Reserved. This file defines the user class. This class is used as authentication object for the user. @module User.h - User class definition. | @doc
The one of importance is the module tag. It accepts two entries following it. The abstract and the description, divided by a standard autoduck separator |.
The tags uses are @class descriptor, @access descriptor and @cmember (class member) descriptor. These tags all describe the code that follows and must be put directly preceding it or else will be ignored or error reported. After parsed with autoduck the output is:
22 Coding Conventions for C++
Sample:
// code inside of User class //@mfunc string | User | Login | This function gets the //login name for the user object. std::string Login(void);
Result:
User::Login
std::string User::Login(void) This function gets the login name for the user object. Defined in: C:/JOB/EON/AUTODUCK/TEST/USER.H
For even more details one can use separate comment in implementation (cpp) file in front of function body.
Sample:
////////////////////////////////////////////////////////////////////// // // // // // // // // // @ex The following example demonstrates how to use the function: | // string sLogin="Tomaz"; // User aUser; // // set user login name // // // // ////////////////////////////////////////////////////////////////////// void User::Login(string sLogin) { _sLogin = sLogin; } @todo Nothing to do. But a nice tag demonstration. aUser.Login(sLogin); @rdesc Function returns no value. @parm string | sLogin | Specifies new login name. @mfunc This function sets user object login name given by parameter <p sLogin>.
User::Login
void User::Login(string sLogin) This function sets user object login name given by parameter sLogin. Defined in: C:/JOB/EON/AUTODUCK/TEST/USER.CPP Return Value Function returns no value. Parameters sLogin Specifies new login name. Example The following example demonstrates how to use the function:
string sLogin="Tomaz"; User aUser; // set user login name aUser.Login(sLogin);
Samples:
IDS_FILE_NOT_FOUND ID_FILE_OPEN IDC_EDIT_USERNAME IDC_CBO_USERGROUP IDB_COMPANY_LOGO
In this situation the compiler needs not to calculate the size of Person to reserve space for it inside the class since it only reserves space for pointer. And it knows the pointer's size. Hence you do not need to include the Person header file. You just need to declare the name. The same is true for the reference (&) since the reference is nothing more than constant pointer.
By including person.h only to implementation (.cpp) file you effectively assist the compiler in pre-compilation of header files.
Object 1
Object 2
Object 3
Sample:
class Person { }; // user is a person with login and password class User : public Person { string sLogin_; string sPassword_; };
Mix in class is a pure virtual and is a technique used to "demand" interface from certain class. It should be recognizable by class name which should not be a noun but instead behaviour descriptor (an adjective), such as: Serializable, Persistent, Listable,...
Sample:
// listable is an interface, not a class class DynamicallyCreatable { virtual void Create()=0; } // using mixin instead of real inheritance class Person : public User, public DynamicallyCreatable { // this class is forced to implement interface DynamicallyCreatable // i.e. the method Create() }
A rule is that first inheritance is *real* inheritances and follow inheritances are mix-ins.
Sample:
class User : public Person, public DynamicallyCreatable, public Persistent { }
Exceptions
Exceptions are powerful and structured mechanism that makes your code concise and easier to understand. Here is an example of code that does not use exceptions:
void fn1() { if (fn2()) { ok(); } else { ReportError(); } } boolean fn2() { if (fn3()==1234) return true; else return false; } int fn3() { if (fn4()!=2) return 1234; else return 4321; } int fn4() { return random()*2+1; // 1 or 2 }
Typically in your program you might have hierarchy of calls. If return codes are used as mechanism to return errors then checks have to be made throughout hierarchy and error codes translated several times to come to the top hierarchy.
The situation becomes even worse when error handling has to consider other people's object hierarchies.
There is however penalty on exceptions. They are C++ mechanism and should not cross C++ boundaries. When working with COM one should always make sure that all exceptions are caught and translated to OLE errors before application boundaries are crossed.