5.1 Build System (Prev) 5.3 Parameter File I/O (Next)
This file documents programming and formatting conventions that should be used in the source code of Simpatico:
Files
- Header files name use file extension *.h and source (implementation) files use *.cpp.
- Write one public class per file.
- The base name of the header and implementation files should be the class name: The header and source files for a class named MyClass should be MyClass.h and MyClass.cpp.
- Place the header and source file for each class in the same directory.
- Use header guards in all header files.
Symbol Names
Code Formatting:
- Indent exactly 3 spaces per level. Do NOT use any tabs.
- For control structures (if, for, while, etc.), place the opening brace at the end of a line, and the closing brace on a line by itself, aligned with the beginning of the opening line, like this:
for (int i = 0; i < end; ++i) {
doSomething();
}
- For functions with more than one line, put the opening brace on a separate line, and align opening and closing braces, like this:
int SillyClass::sillyMethod(int param1, int max)
{
for (int i = 0; i < max; ++i) {
param1++;
}
if (param1 > 0) {
return 0;
} else {
return 1;
}
}
- For one-line functions, the function definition may be given on a single line, like this:
inline int SillyClass::data()
{ return data_; }
- Set off the operators =, ==, <, >, +, and - by one space on either side. Multipication (*) and division (/) operators may or may not be set off by white space. Make occasional exceptions to avoid line wraps.
- Use one space to separate keywords, parentheseses, and opening braces in conditional statements. Use one space after each semi-colon in for-loop statements, and one space after each comma in function parameter lists.
- Do not follow opening parentheses or precede closing parentheses by a space. Do not add whitespace space before commas or semicolons.
- Break lines at 80 characters or less whenever possible, to preserve readability in printouts.
- Wrap every source file in the src/ directory in a namespace block. Start the namespace declaration in the first column.
- Consecutive function declarations or definitions within a file, along with associated documentation blocks, should be separated by a single blank line.
- In class definitions, align "public:", "protected:", and "private:" declarations with the beginning of the class definition statement, and with the closing brace. List public members first, then protected, then private. Within each block, list data members first, then methods.
- List any friend declarations at the end of a class definition in a "pseudo-block" that is preceded by a comment "//friends:" on a line by itself, after the private members. The "//friends:" comment should be aligned with "public:" and "private:" declarations.
- Inline method definitions should be given outside the class definition, within the header file. The word "inline" should be added to the function definition, but not the function declaration.
- Example (with doxygen documentation):
#ifndef UTIL_SILLY_CLASS_H
#define UTIL_SILLY_CLASS_H
namespace Util
{
/**
* A truly pointless class.
*/
class SillyClass : public Base
{
public:
/**
* The first method.
*
* \param param1 a globble
* \param param2 a gloob
*/
int method1(int param1, double param2);
/**
* Get buddy (by reference)
*/
const Buddy& buddy() const;
protected:
/**
* Get buddy by non const reference)
*/
Buddy& buddy();
private:
int data1_;
Buddy* buddyPtr_;
//friends:
friend class Buddy;
};
// Inline methods
inline const Buddy& SillyClass::buddy() const
{ return *buddyPtr_; }
inline Buddy& SillyClass::buddy()
{ return *buddyPtr_; }
}
#endif
Documentation (via doxygen):
Simpatico uses the doxygen (www.doxygen.org) utility to create html documentation from documentations blocks that are extracted from the source code. Doxygen will extracts any multi-line comment that begin with a slash and two asterisks ("/**"), or single line comments that begin with three slashes ("///"), like this:
/**
* This comment will be extracted by doxygen (note the extra asterisk)
*/
/// So is this one (note the extra slash)
/*
* This comment, however, will not be extracted by doxygen.
*/
// Nor will this one.
Comments within functions that you do not wish to be extracted by doxygen should use the usual form for C comments, using only a single asterisk for multi-line comments or only two slashes for for single line comments.
- Create dOxygen documentation blocks for public and protected named quantities, i.e., all classes, public and protected member functions, protected member variables, namespaces, global functions, typedefs, enums, and constants.
- The doxygen documentation block for a class should appear immediately above the first line of the class definition.
- The doxygen documentation block for a class member function should be immediately above the function declaration, within the class definition, in the header file.
- Prefer doxygen multiline comments for documentation of classes and class member functions.
- Document all parameters of public and protected functions, using the dOxygen param keyword.
- The documentation for every class and public or protected function should begin with a brief single-sentence synopsis, which must end with a period and be followed by a blank line. This brief description is often sufficient. If needed, more detailed discussion may be given in one or more subsequent paragraphs, separated by blank lines. Example:
/**
* Align the universe.
*
* This is a longer discussion of what the method does, and of how and
* why it does it. It may also contain a discussion of assumptions, and
* of the algorithm.
*
* A longer discussion may contain two or more paragraphs, separated by
* blank lines.
*
* \param thing1 value of some quantity
* \param thing2 flag to determine what to do with thing1
* \return shift in the position of the universe
*/
double alignUniverse(double thing1, bool thing2);
- Document private class member variables using either the multi-line or single line doxygen format. This does not normally appear in the html documentation, but can be included if desired. Documentation of the meaning of member variables is important for developers who must read the implementation, though not strictly necessary for users.
- Do not use doxygen-style comments for the function definitions (implementations), when the definition is given outside the class definition. Use a conventional C/C++ comment format (with one asterisk or two slashes) above each function definition, like this:
/*
* Brief reminder of purpose. Possibly comments on implementation.
*/
void MyClass:myFunction(int param)
{
// Do something here
}
Interface Design
- All nonstatic class member variables should be private or (occassionally) protected.
- Pass and return primitive C/C++ data types by value. Pass primitive data types to functions by non-const reference only when they must be modified within the function.
- Pass objects (class instances) to functions by reference, not by value. Pass by const reference if the object is not modified.
- Prefer references over pointers as function parameters. Pass pointers to functions only if: i) a null value for the pointer is a meaningful possibility, or ii) the pointer contains an address that must be re-assigned within the function.
- Prefer references over pointers as function return values. Return a pointer only if a null pointer is a meaningful possibility.
- Practice strict "const correctness". Mark function parameters and class member functions as const whenever possible.
- Read-only access to a member variable of primitive type should be provided (when needed) by an accessor function that returns the member variable by value. Read-only access to an object (class instance) that is owned by a class may be provided by an accessor function that returns the object by const reference. Read-write access to an object may be provided by an accessor that returns the object by non-const reference. Simple accessors that return by value or const references should be marked const. For example, if a class has an int member variable data_ and a member object_ that is an instance of class Object, you might consider providing any or (none) of the following methods:
int Thing::data() const
{ return data_; }
const Object& Thing::object() const
{ return object_; }
Object& Thing::&object_()
{ return object_; }
- Providing an accessor function that returns a non-const reference to a member object is equivalent to making the member public, and should be used when this is what is desired. Do not simply make the data member public. For example, the McMd::Atom and McMd::McSimulation classes provide the following methods:
{ return system_; }
{ return system_; }
These provide pseudo-public access to the position of an Atom (represented as a Util::Vector) and to the system component of an McSimulation. The advantages of this scheme are:
-
Uniform interface: All members must be accessed through accessors, so users need not remember which members are public and which are accessed through accessor functions.
-
Uniform name conventions: The name of the accessor that returns a member is always the name of the member variable, without amy underscore. This allows us to use an underscore to mark member variable names, without exposing underscored names outside the class implementation.
-
Uniform access: The same name convention is used for accessors that return by value, const reference, or reference, but the compiler can still enforce access control.
-
Implementation hiding: The same convention is for functions that access members of a class and those that access objects athrough a pointer. Use of accessor functions also makes it possible to add and remove sanity checks for debugging, such as checks that pointers are not null.
- Write access to a class instance may sometimes instead be provided by an explicit "set" function. The use of a set function is preferable when one needs to check preconditions or carry out operations necessary to guarantee data validity.
Data Structures
- Use the C++ iostream classes for all file IO. For consistency, avoid the C fscan() and fprint() methods. Use the wrapper classes in the src/format directory to simplify coding of formatted output to ostreams.
- Use std::string to represent character strings whenever possible.
- Use Util::Vector objects to represent Cartesian positions and separations, or any array floating point numbers for which the number of elements must equal the dimensionality of space (e.g., the dimensions of the Boundary for a periodic orthorhombic unit cell). Use IntVector to represent corresponding arrays of integers.
- Prefer the array container templates in src/util/containers over bare C arrays and STL containers. STL containers such as std::vector should be used when their is a need for their resizability. Our preference for the home-made containers is based on the fact that:
-
The Simpatico containers provide optional bounds checking, which is enabled only when NDEBUG is not defined.
-
Unlike std::vector, Simpatico array classes are not automatically resizable, and so will never be silently moved to a new location in memory.
The lack of resizability is essential for algorithms that store pointers to particular objects within an array, since the address of an object that is stored in resizable array such as a std::vector generally changes whenever the array is resized.
Header File Includes
- In header files, use forward class declarations rather than including class headers when possible. Forward declarations are sufficient for classes or class templates that used only in function declarations (as parameter or return types) or in the declaration of types for pointer member variables. Header files are generally necessary for base classes, member objects, and classes that are used in the implementation of inline functions.
- Header files that provide information required in another C++ file should normally be included explicitly, even if they would be indirectly included via another included header file. Reliance on indirect inclusion is fragile, and explicit inclusion helps document the dependency. Exceptions are:
-
A class implementation file MyClass.cpp may rely on indirect inclusion of header files that are included by the corresponding header file MyClass.h
-
A derived class may sometimes rely on indirect inclusion of header files that are included by a base class.
-
The Exception.h header is should always be included indirectly by inclusion of src/util/global.h.
- Use explicitly qualified names for standard library symbols such as std::string in function parameter lists and return values. Use this convention both in function declarations and functions definitions.
- Never use a "using std" statement to load the entire C++ standard library namespace into a header file.
Error Handling
- Include the file <util/global.h> in all files that use C assert() statements or that can throw exceptions. This automatically includes the "Exception.h" and "Log.h" header files, defines the UTIL_THROW(..) macro that is used to throw exceptions, and enables assert() statements if UTIL_DEBUG is defined,
- For checks that are intended only for debugging, use C assert() statements or enclose the test in a ifndef UTIL_DEBUG .... endif conditional compilation block.
- Use the UTIL_THROW macro defined in src/util/global.h to throw Exception objects for all errors, except those caught in debugging mode by C assert() statements. Like the standard assert() macro, this macro prints a message and showing the file and line number from which an Exception is thrown, as an aid to debugging.
- Do not Exceptions for control flow. Exceptions should be used only for errors, and should normally cause the program to terminate.
5.1 Build System (Prev) 5 Developer Information (Up) 5.3 Parameter File I/O (Next)