All code must >parse
successfully. If it doesn't, try to use a construct
that the parser understands. Failing that, enhance the parser (or ask for
it to be enhanced) to support the missing language feature.
Use the >check
command to help determine whether software follows
recommended guidelines. It supports many of the guidelines in this
document, and the >fix
command can even interactively repair some
of them.
The existing software does not always follow every guideline. Sometimes,
there is a good reason to violate a guideline. In others, the effort that
would be needed to make the software conform is better spent elsewhere.
Some of the software was developed before C++11, so there are things that
it should still adopt (greater use of unique_ptr
, for example).
Try to make it impossible for the reader to tell where code was added or changed.
- Begin each file with the following heading:
//================================================================================
//
// <FileName.ext>
//
// Copyright (C) 2013-2022 Greg Utas
//
// This file is part of the Robust Services Core (RSC).
//
// RSC is free software: you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the Free Software
// Foundation, either version 3 of the License, or (at your option) any later
// version.
//
// RSC is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
// details.
//
// You should have received a copy of the GNU General Public License along
// with RSC. If not, see <http://www.gnu.org/licenses/>.
-
Use spaces instead of tabs.
-
Indent a multiple of 3 spaces.
-
Remove unnecessary interior spaces and semicolons. Remove trailing spaces.
-
Use
//
comments instead of/*...*/
. -
Add blank lines for readability, but do not use consecutive blank lines.
-
Limit lines to 80 characters in length. Break after
,:)
and before(
. A break at an operator may occur before or after, depending on which seems to read better. -
Break before
{
and}
unless everything in between also fits on the same line. -
Almost always use PascalCase. Use all uppercase and interior underscores only in low-level types and constants. Names that evoke Hungarian notation are an abomination.
-
Keep
*
and&
with the type instead of the variable (Type* t
instead ofType *t
).
-
Insert an
#include
guard based on the file name (FileName.h andFILENAME_H_INCLUDED
) immediately after the standard heading. -
Sort
#include
directives alphabetically within the following groups:- header(s) that define base classes of classes defined in this header
- external headers (
#include <filename>
) - innteral headers (
#include "filename.h"
)
-
Remove an
#include
solely associated with functions inherited from a base class. -
Remove an
#include
by forward-declaring a class that is only used with references or pointers. Use an explicit forward declaration instead of relying on this as a side effect of a friend declaration. -
Avoid
using
declarations and directives forstd
symbols and those of other namespaces. Prefix the namespace directly (std::<symbol>
, for example). -
Initialize global data (static members) in the .cpp if possible.
Only use the preprocessor for one of the following purposes:
-
An
#include
guard. -
Conditional compilation (
#ifdef
). The symbols are defined when the compiler is launched. Those in current use areOS_WIN
andOS_LINUX
for Windows and Linux. These specify the target platform and may only be used in platform-specific files, which are named *.win.cpp and *.linux.cpp.FIELD_LOAD
for a production build (else assumed to be a lab build). May only be used in a .cpp that executes before the configuration file is read during system initialization; otherwise useElement::RunningInLab()
.WORDSIZE_32
for a 32-bit CPU (else assumed to be 64-bit). May only be used in subs/ files.CT_COMPILER
when running the>parse
command. May only be used in subs/ files.
-
To
#define
an imitation keyword that maps to an empty string. The only current examples areNO_OP
andNO_FT
.
-
Sort
#include
directives alphabetically within the following groups:- header(s) that declare something that this .cpp defines
- header(s) that define direct base classes of classes defined in this .cpp
- external headers (
#include <filename>
) - interal headers (
#include "filename.h"
)
-
Omit an
#include
orusing
that is already in the header. -
Put all of the code in the same namespace as the items declared in the header.
-
Implement functions in alphabetical order, after the constructor(s) and destructor.
-
Separate functions that belong to the same scope with a
//----
... that is 80 characters long. -
Separate classes and functions that belong to different scopes (local to the .cpp) with a
//====
... that is 80 characters long. -
Add a blank line after the
fn_name
that defines a function’s name forDebug::ft
invocations. -
Name constructors and destructors
"<class>.ctor"
and"<class>.dtor"
forDebug::ft
invocations. -
Fix compiler warnings through level 4.
-
Give each class its own .h and .cpp unless it is trivial, closely related to others, or private to an implementation.
-
A base class should be abstract. Its constructor should therefore be protected.
-
Tag a constructor
explicit
if it can be invoked with one argument. -
Make each public function non-virtual, with a one-line invocation of a virtual function if necessary.
-
Make each virtual function private if possible, or protected if derived classes may need to invoke it.
-
Make a base class destructor
- virtual and public
- non-virtual and protected
- virtual and protected, to restrict deletion
-
If a destructor frees a resource, even automatically through a
unique_ptr
member, also define- a copy constructor:
Class(const Class& that);
- a copy assignment operator:
Class& operator=(const Class& that);
Here, create copies ofthat
’s resources first, then releasethis
’s existing resources, and finally assign the new ones.
- a copy constructor:
-
If a class allows copying, also define "move" functions. These release
this
’s existing resources and then take over those owned bythat
:- a move constructor:
Class(Class&& that);
- a move assignment operator:
Class& operator=(Class&& that);
The implementations typically use
std::swap
. - a move constructor:
-
Each of the above functions can be suffixed with
= delete
to prohibit its use, or= default
to use the compiler-generated default. -
To prohibit stack allocation, delete the constructor and/or destructor.
-
To prohibit scalar heap allocation, delete
operator new
. -
To prohibit vector heap allocation, delete
operator new[]
. -
If a class only has static members, convert it to a namespace. If it is a class so that some members can be private, delete its constructor.
-
Use
override
when overriding a function defined in a base class. -
Within the same level of access control, sort overrides alphabetically, and place them after functions that are not overrides.
-
Make a function or argument
const
when appropriate. -
Remove
inline
as a keyword. -
Avoid
friend
where possible. -
Override
Display
if a class has data. -
Override
Patch
except in a trivial leaf class. -
Avoid invoking virtual functions in the same class hierarchy within constructors and destructors.
-
Provide an implementation for a pure virtual function to highlight the bug of calling it too early during construction or too late during destruction.
-
If a class is large, consider using the PIMPL idiom to move its private members to the .cpp.
-
When only a subset of a class’s data should be write-protected, split it into a pair of collaborating classes that derive from
Protected
andPersistent
(orImmutable
andPermanent
). -
Static member data begins with an uppercase letter and ends with an underscore, which may be omitted if it is not returned by a "Get" function. Non-static member data begins with a lowercase letter and ends with an underscore, which may be omitted if it is public (in which case it should probably be in a
struct
).
-
Use the initialization list for constructors. Initialize members in the order that the class declared them.
-
Use
()
instead of(void)
for an empty argument list. -
Name each argument. Use the same name in the interface and the implementation, and in the base class and its overrides.
-
Make the invocation of
Debug::ft
the first line in a function body, and follow it with a blank line. -
The
fn_name
passed toDebug::ft
and other functions must accurately reflect the name of the invoking function. -
A simple "Get" function should not invoke
Debug::ft
unless it is virtual. -
Left-align the types and variable names in a long declaration list.
-
Use
nullptr
instead ofNULL
. -
Check for
nullptr
, even when an argument is passed by reference. (A reference merely documents thatnullptr
is an invalid input.) -
After invoking
delete
, set a pointer tonullptr
. -
Use
unique_ptr
to avoid the need fordelete
. -
Use
unique_ptr
so that a resource owned by a stack variable will be freed if an exception occurs. -
Declare loop variables inline (
for auto i =
, for example). -
Declare variables of limited scope inline, as close to where they are used as is reasonable.
-
Use
auto
unless specifying the type definitely improves readability. -
Include
{
...}
in all non-trivialif
,for
, andwhile
statements. -
When there is an
else
, use braces in both or neither clauses of theif
. -
When there is no
else
, use braces for the statement afterif
unless it fits on the same line. -
Define string constants in a common location to support future localization.
-
To force indentation, even in the face of automated formatting, use
{
...}
between function pairs such asEnterBlockingOperation
andExitBlockingOperation
Lock
andUnlock
MakePreemptable
andMakeUnpreemptable
-
It is a serious bug for a function to cause an unexpected exception, so check arguments and results returned by other functions.
-
Throw an exception only when it is impossible to fail gracefully or the work being performed must be aborted. Prefer to generate a log (
Debug::SwLog
) and return a failure value.
Some comments identify work items. They have the form //a
, where a
is some character. The following are currently used:
//&
is something inmain.cpp
that might be enabled for a subset build//>
is an internal constant that can be changed to raise or lower a limit//@
is a useful breakpoint during development//L
is a Linux target enhancement//b
is a basic call enhancement//c
is aCodeTools
enhancement//d
is a decoupling enhancement//e
is an unclassified enhancement//p
is a POTS enhancement//x
is something to be removed (should not appear in a Release)//*
is something to be fixed or implemented as soon as possible (should rarely appear in a Release)