C++ is a big and complex language that's why before starting new project it is very important to set up rules that whole team must follow.
Here is rules that my team follows:
#_______________________________________________________________________________ # # README #_______________________________________________________________________________ # 1. Prerequisites 2. Building 3. Installing 4. Project structure 5. Code convention 6. Tools 7. Contributing #_______________________________________________________________________________ # PREREQUISITES This project requires: * Cross-platform CMake v2.8.1+ * GNU Make or equivalent. * GCC or an alternative, reasonably conferment C++ compiler. * Boost C++ Libraries v1.42+ [HEADERS and LIBRARIES] * Qt Framework v4.5+ * glog v0.3+ * gtest v1.4+ All include are located in $(PROJECT_ROOT)/extern as symbolic links to installation directories so we can use them in unified way. 1. boost http://www.boost.org/doc/libs/?view=categorized 2. googletest http://code.google.com/p/googletest/ 3. google-glog http://code.google.com/p/google-glog/ (optional) 4. gmock http://code.google.com/p/gmock/ 5. soci http://soci.sourceforge.net/ #_______________________________________________________________________________ # BUILDING This project uses the Cross-platform CMake build system. However, we have conveniently provided a wrapper configure script and Makefile so that the typical build invocation of "./configure" followed by "make" will work. For a list of all possible build targets, use the command "make help". NOTE: Users of CMake may believe that the top-level Makefile has been generated by CMake; it hasn't, so please do not delete that file. #_______________________________________________________________________________ # INSTALLING Once the project has been built (see "BUILDING"), execute "sudo make install". #_______________________________________________________________________________ # PROJECT STRUCTURE Given project template contains general solution. You can remove or add some directories. For example you can add "gen" directory for all generated files or remove "lib" if some package doesn't produce library code. To illustrate structure with example I added three directories (all with suffix .package). You can remove them by searching project for .package or rename and reuse Make/CMake files. Here is the basic source structure of the project: tools - Directory for scripts and CMake modules bin - holds scripts for code building and checking share - resources that are needed by tools (like CMake modules) doc - Output of doxygen and other documents related to project Contains all Doxygen generated documents(removed by distclean) and project main page extern - Directory holds external libraries that are common for all packages as symbolic links. Keep there only libraries that are not in OS distribution or if you want to control versions. It's not allowed to be modified. For example: ln -s /opt/gtest-1.4.0 /opt/gtest How to install external libs: 1. Build them using prefix. For example boost: TIP: It is convenient to use one prefix for libraries followed by lib name ./bootstrap.sh --prefix=/opt/boost-1.42.0 ./bjam toolset=gcc install ln -s /opt/boost-1.42.0 /opt/boost 2. Set the ROOT directory of a library in your environment so CMake/Makefile can easily find/use them. For example: export BOOST_ROOT=/opt/boost TIP: If Boost is big for you take a look on very useful tool "bcp" or build only required: ./bootstrap.sh --with-<library_name> TIP: If CMake can't find some module try: cmake --help-module <module_name> (e.g. FindBoost) 3. Put libraries location in LD_LIBRARY_PATH. Compiler search first here. export LD_LIBRARY_PATH = .:path/to/lib/directory 4. Make symbolic link: cd extern ln -s /opt/Qt Qt 5. If you have third party library that is used from many packages and it is possible/want to modify create directory "src/thirdparty" and put it there base - Common macros, base classes and useful templates used through project "package" - Directory for a independent static/dynamic library or sub-project bin - Holds the output of compile process. bin.debug (ignored by svn) doc - Package specific documentation include - Package API. Include files that will be used from other packages. (automatically transferred) lib - Holds the output of build process if the result is a library (shared library by default) share - Holds shared resources sub-package - Code that is part of the enclosing package TIP: Use nested namespaces for sub-package thirdparty - Holds the third party sources that could be modified for needs of library . - All code has to be located here TIP: Makefile or CMake will copy all public API (header files) in include directory. Doxygen skip it to avoid duplications. test - Holds simple tests for the package (all test files are excluded form doxygen) TIP: You can divide tests as: unit integration end-to-end data - Data that will be used by test (optional) Control template instantiations - No other file in the project should generate any specialized function code even if the source code is available when the file is compiled (see g++ fno-implicit-templates parameter). Every template specialization that is required is generated in one separate file and linked with the other modules in the final link phase. Also remember that generation of the same function specialization (by compiler or the user) more than once in compilation unit is an error. GOOD PROGRAMMING PRACTICES: * During development you can use Makefile that is located in packages! * Locate all common template classes in base directory * Schemes for reducing template code size - cut member functions that do not depend on the type of the template argument - use counted pointers * Release to Release binary compatibility (RRBC) rules - the existing class hierarchy must remain unchanged - declarations for new virtual functions must appear after the declarations for all pre-existing virtual functions - all old virtual functions must remain and be declared in the same order - non-virtual functions and static member functions can be added without restriction - previously existing public or protected functions must continue to exist - the total size of an instance of a class must remain unchanged, and all public or protected data members must continue to exist and must remain at the same offset within the class. Private date that is referenced from public or protected inline members functions count as public data. #_______________________________________________________________________________ # CODE CONVENTION C++ code convention resources: http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml (use tools/bin/cppling.py) with combination/or "The Elements of C++ Style" book 1. Naming Your Files 1.1 Each class definition should be in its own file where each file is named directly after the class's name (like in Java). ClassName.h // defines class ClassName ClassName.cpp 1.2 Use separate files for each namespace-scope class, struct, union, enumeration, function, or overloaded function group Give each file the same name as the element it contains, using the same case. Consider also using separate files for template specializations. 1.3 PkgConf/PkgDoc files in projects are for package configuration/documentation, AppConf/AppDoc for application as a whole. 2. Namespaces 2.1 Every module, package in a project has own namespace. Namespaces simply wrap all enclosed names with another name and they group logically related entities. Namespace name should not correspond to a single entity but rather to a category to which names in the namespace logically belong. 2.2 Namespace names are all lower-case, and based on project names and possibly their directory structure. Here is the name convention: project_subdir. In this way we mimic directory structure. 2.3. It's usually sufficient to have one or two levels of namespace, with the odd extra level for internal details. 2.4 Use unnamed namespaces instead of static to hide local functions and variables 3. Classes 3.1 The object life cycle involves three activities: creation, destruction, and assignment! 3.2 Always implement destructor, copy constructor and copy assignment operator (the rule of three) plus constructor and do not forget to call them from derived classes if use inheritance. Use DISALLOW_COPY_AND_ASSIGN macros to disallow copy and assignment. 3.3 Be very careful especially when you have a pointers to dynamically allocated memory in your class * A constructor to either make an initial allocation or set the pointer to NULL. * A destructor to delete the dynamically allocated memory. Destructors are not inherited, and you cannot explicitly call a destructor of a base class, so define them virtual. * A copy constructor to make a copy of the dynamically allocated memory (deep copy). * An overloaded assignment operator to delete previous allocated and make a copy of the dynamically allocated memory (deep copy). You can think of an assignment operator as a combination of a destructor and a copy constructor. And do not forget to check for self assignment! An overloaded assignment operator defined in a derived class doesn't automatically invoke the assignment operator for it's base class part so do not forget to call assignment operator of the base class! 3.4 Keep order:class ClassName { <friends classes> public: // Behaviors (methods) that you want clients to use. // Method declarations // Member declarations protected: // Methods of a subclass can call a protected method // or access a protected member of an object // Method declarations // Member declarations private: // Only if you want to restrict a access from subclasses // Method declarations // Member declarations };3.5 Naming convention * The naming of the class always starts with a capital letter. * If it is an interface(pure virtual) starts with "I". * For all class members use underscore prefix '_' e.g _speed all other are camelCase. * Class methods are camelCase(). * Give Function Parameters the Same Name as the Member Variables You Assigned Them toclass Customer { public: Customer(const string& name); void setName(const string& name); private: string _name; };* Constant types with 'k' e.g. kDefaultSpeed * Static types with 's'. * Free functions(not in a class) has to be in lower case using '_' and located in file that starts with lower case. Good practice is to include file name in free functions name. For example in file vmdsock.cpp to have methods: vmdsock_init(); vmdsock_create(); * Pluralize the Names of Collections * Use "other" for Parameter Names in Copy Constructors and Assignment Operators * Use size_t where positive "int" numbers are required e.g. method parameters and loops 3.6 Consider the unit tests to be part of the actual implementation. Corresponding test class(test functionality) is in file which name starts with 'test' and it is located in test directory. It is good practice to group test and GTest framework allows that. Example of test grouping: Fine-Grained Test: testClassName.cpp // contains tests for ClassName functionality, class API Medium-Grained Tests: testInsertBadData() Large-Grained Tests: testInsert() 3.7 To test classes for creation, assigning and all overloading operations create class with main method: ClassNameTest.cpp // easily you can run all static checkers on it! or create something like that:#ifdef __TEST__ int main(int argc, char *argv[]) { ... #endifRun a program transparently, but print a stack trace if it fails.gdb -batch -ex "run" -ex "bt" ${my_program} 2>&1 | grep -v ^"No stack."$For automated unit tests we wanted our program to run normally, but if it crashed, to add a stack trace to the output log. 3.8 If constructor takes only one parameter make it explicit. This will prevent confusing automatic conversions....... explicit Car(const Driver& driver) {} .......3.9 Place each member variable in the initialization list in the same order as they fall in the class declaration Initialization order is declared in the class itself, not in the constructor! That is because of caveat with initializer lists: they initialize data members in the order that they appear in the class definition, not their order in the initializer list. 3.10 Think about using composition before using inheritance. 3.11 If you choose inheritance, keep following rules: * Constructor must not call virtual functions. * If class has even one virtual function implement virtual destructor * Keep in mind that constructors, assignment operator and friends methods cannot be inherited * Do not forget to call base constructor(initialize base), base copy constructor and base operator= first 3.12 Use mix-in (-able e.g. Callable) classes instead of multiple inheritance. 3.13 When you want class to be "abstract", define destructor as virtual. In this way prevent a class from being instantiated. 3.14 Think of private inheritance as "implemented using". See AddapterPattern (GoF) NOTE: The Liskov Substitution Principal states that it should always be possible to substitute a base class for a derived class without any change in behavior. If you want to break this principal you can use private inheritance. 3.15 Declare Enumerations within a Namespace or Class To avoid symbolic name conflicts between enumerators and other global names, nest enum declarations within the most closely related class or common namespace. If this is not possible, prefix each enumerator name with a unique identifier such as the enumeration or module name. 3.16 Accept objects by reference and primitive or pointer types by value 3.17 Use a non-virtual function for behavior that is common to all classes in the hierarchy and should not be overridden in derived classes. 3.18 Use a virtual function(and destructor) to define default behavior that may be overridden in derived classes. 3.19 When picking data members for a class careful choose between: pointer(very flexible), reference and value. If polymorphic behavior of data member is essential, use reference or a pointer as data member. GOOD PROGRAMMING PRACTICES: * Use size_t variables for simple loop iteration and array subscripts * When you write a subclass, you need to be aware of the interaction between parent classes and child classes. Here is the construction order: - The base class, if any, is constructed C++ will automatically call the default constructor for the parent class if one exists. But you can chain the constructor just as when initializing data members in the initializer list - Non-static data members are constructed in the order in which they were declared - The body of the constructor is executed * It is good style to repeat the virtual specification in a derived class * Use default arguments to reduce the number of constructors * Avoid using large arrays as automatic variables, use pointer to array * Use QuickCheck++, Boost.Contract++ 4. Documenting and logging 4.1 Put all documentation of methods and classes in header files in source files document implementation issues if necessary. 4.2 Use Java stile of documenting, doxygen will make documentation. GLOG is not just logging levels. It is much more, for example conditional logging, check macros, debug support. For more information see: http://google-glog.googlecode.com/svn/trunk/doc/glog.html 5. What to inline? -small methods, such as accessors for private data -functions returning state information about object -small functions that are called repeatedly GOOD PROGRAMMING PRACTICES: * Place the method definition directly in the class definition and rarely use "inline" keyword. 6. What to be in header files? 6.1 The header files should never contain definitions of functions or variables with the exceptions of inline functions. 6.2 They should not contain definitions of unnamed namespaces. The header file may contain declarations of any names that have internal linkage, including const identifiers; types defined by typedef, class, struct, and enum; inline functions; named namespaces; and template declarations and definitions. 6.3 A header file essentially should contain only interface information or specification of some implementation file(s) of a library. In other words, everything included in a header file only serves as reference, and should not inflict any byte in the executable. But of course you can put some methods that will be inlined. 7. Includes 7.1 Use following order:#include <corresponding .h file> #include <C headers> #include <STL headers> #include <"external" libraries> // third party code #include <"internal" libraries> // from other packages <forward references>7.2 Start include declaration from the root for the project: #include "helper.package/include/subject_extract.h" 7.3 A file should never depend on an implementation file; in other words, you should never write: #include "something.cpp" 7.4 In implementation .cpp file do not write #includes that are already included in header files 7.5 Don't include a header file when a forward declaration is all you need GOOD PROGRAMMING PRACTICES: * If you use a lot of forward declarations you might choose to prefer using boost::checked_delete. 8. When to use friend methods? -use them for testing private data stuff NOTES: 1. Also you can use simple hack: g++ -Dprivate=public ..... 2. GTEST has a special macro for mark test methods as friends - FRIEND_TEST -for overloading operators to reflect syntax -for methods that you don't want to become part of class interface -provide better protection than other techniques when functions need to communicate with more than one class. For example:/** * This provides a neat solution to the problem: * the multiply function has access to private data in * both classes but we have not "opened them up" to anyone else. */ class Vector { friend Vector operator* (const Matrix& lhs, const Vector& rhs); .... } class Matrix { friend Vector operator* (const Matrix& lhs, const Vector& rhs); .... } // free function Vector operator* (const Matrix& lhs, const Vector& rhs) { .... }GOOD PROGRAMMING PRACTICES: * Define friends methods in .cpp file(there is no need to repeated friend keyword) 9. Be Const Correct Const is a powerful tool for writing safer code, and it can help compiler optimizations. Your default choice for passing objects as parameters should be const reference. Only if you explicitly need to change the object should you omit the const. 10. Hungarian notation The right way to use it is to invent prefixes that provide useful information: the prefix indicates the "intent" rather than the "type". In particular, variables with the same C++ type but with different roles often have different prefixes. For example: In a graphics application, variables representing coordinates have prefixes x, y, and, z. Then xCar, yCar, zCar (they all integers) is more meaningful. 11. Implementation hiding 11.1 Use PIMPL(pointer to implementation) Idiom for public API Overcome the language's separation anxiety: C++ makes private members inaccessible, but not invisible. Where the benefits warrant it, consider making private members truly invisible using the Pimpl idiom to implement compiler firewalls and increase information hiding. 11.2 Hide reference to a "secret" class. Use fallowing is the technique: FileOps.h - typical approach// "Cache" is the secret class class FileOps { public: FileOps(const string, Cache&); ........ private: string _fileName; ...... Cache& _cache; }FileOps.h that hide secrets// "Cache" is the secret class class FileOps { public: FileOps(const string inFileName); ........ private: string _fileName; ...... Cache* _cache; // not Cache& }FileOps.cpp............ FileOps::FileOps(const string inFileName) : _cache(new Cache()) { ......... }12. Formatting Use Allman brace style (also called exdented style) with modifications: use the curly brace on the same line as the leading statement, except in the case of a function, class, or method name. GOOD PROGRAMMING PRACTICES: * Separate logical blocks in files with some comment separator * For emacs buffer aligning use 'google-c-style.el' * YASnipped plug-in for Emacs could be used 14. Ownership! When passing an object to a method, you have to think fairly carefully about whether you are transferring ownership. When the ownership(= responsibility to destroy) of the object is passed to another function, it is usually better to use a smart pointer, such as std::auto_ptr.void manage(std::auto_ptr<T> t) { ... } ... // The reader of this code clearly sees ownership transfer. std::auto_ptr<T> t(new T); manage(t);If a call to library returns memory pointers, who is responsible for freeing the memory: the caller or the library? If the library is responsible, when is the memory freed? Think about! References clarify ownership of memory! If you are writing a method and another programmer passes you a reference to an object, it is clear that you can read and modify(if not const) the object, but you have no easy way of freeing its memory, so this is a sign that you are not responsible for destroying. GOOD PROGRAMMING PRACTICES: * As much as possible, avoid returning a reference from a function because - you can't indicate object creation failure - created object could not be local. Who owns it? * Use reference or pointer only if you want to return something polymorphic * If you need reference counting smart pointer see SmartPointer or share_ptr<T>. * If function do pointer arithmetic with parameter it is better to be defined as array e.g. void X::f(T agrp[]) * If you want to forbid pointer arithmetic and delete just make it const e.g. void X::f(T* const agrp) 15. Prefer shared libraries If you rapidly change the API then you need to recompile all of the programs that use the library in both the shared and the static case. If you change the library but do not change the API then you must recompile all the programs that use the library only if they are linked statically. 16. User object pool for large number of short-lived objects If you know that your program needs a large number of short-lived objects of the same type, you can create a pool, or cache, of those objects. See ObjectPool and Flyweight(GoF) * You need to frequently create and destroy objects. * They are similar in size. * Allocating them on the heap is slow or could lead to memory fragmentation. * Each object encapsulates a resource such as a database or network connection that is slow to acquire and could be reused. 17. Exceptions When harmed, take exception: Prefer using exceptions over error codes to report errors. Use status codes (e.g., return codes, errno) for errors when exceptions cannot be used, and for conditions that are not errors. Use other methods, such as graceful or ungraceful termination, when recovery is impossible or not required. GOOD PROGRAMMING PRACTICES: * Use RAII programming idiom * If your code is with exceptions use auto_ptr for all local variables to make your code exception save. * Throw by value, catch by reference 18. When to use parametrized types vs inheritance and delegation? * use parametrized types when T doesn't affect its behavior and there is no hierarchical structure between T objects * use parametrized types if efficiency is essential(to avoid the overhead of virtual functions) * use instances if the types of objects being operated on are not known at compile time 19. Destructors, deallocation, and swap never fail! Period! Everything they attempt shall succeed: Never allow an error to be reported from a destructor, a resource deallocation function (e.g., operator delete), or a swap function. Specifically, types whose destructors may throw an exception are flatly forbidden from use with the C++ standard library. 20. STL 20.1 Use vector by default. Otherwise, choose an appropriate container 20.2 Store only values and smart pointers in containers 20.3 Prefer push_back to other ways of expanding a sequence 20.4 Check whether an operation invalidates iterators before using it 20.5 Ensure that template arguments satisfy the requirements of the corresponding template parameter. 20.6 Don’t store auto-pointers in STL containers. 20.7 If you are traversing a container without changing its contents, use a const_iterator #_______________________________________________________________________________ # TOOLS Use valgrind, cppcheck and splint to check code http://valgrind.org http://cppcheck.wiki.sourceforge.net http://www.splint.org Wrapper in other language around C++ http://www.swig.org/ Better error messages information: http://www.bdsoft.com/tools/stlfilt.html (optional) Better C++ GDB debug: http://sourceware.org/gdb/wiki/ProjectArcher Alternative compiler: http://clang.llvm.org/ http://dragonegg.llvm.org #_______________________________________________________________________________ # CONTRIBUTING Here is typical project structure: http://code.google.com/p/various-code-fragments/source/browse/#svn/trunk/cpp/project.template #_______________________________________________________________________________ # REQUIRED BOOKS
No comments:
Post a Comment