This file describes some key C++ coding style guidelines for contribution to the LiveCode source code. In the past, C++ was used as an improved version of C, and didn't use the whole of the language's feature set. Since the introduction of C++11 support to the LiveCode build toolchain, some more advanced features of C++ are recommended to be used when developing LiveCode.
See also C++ Feature Usage Guidelines for LiveCode.
-
Variable and function names should be descriptive. Don't be scared of verbosity (but don't go too overboard on symbol lengths).
-
Variable names must be lowercase, with words separated by underscores (
_). They should be prefixed to indicate scope:t_: local variablesp_: in parametersr_: out parametersx_: in-out parametersm_:classorstructinstance member variabless_:class, function, or file-local static variablesg_: global variables
Good:
static RegExp s_regexp_cache[10];Bad:
int x, y, w, h; -
Constant names, including
enummembers, should be title case and prefixed withkMCand the module name.Good:
const MCByteOrder kMCByteOrderHost = kMCByteOrderBigEndian;`Bad:
const int MAX_LENGTH = 20; -
Function names should generally be title case. Public (exported) functions should have names prefixed with
MCand the module name.Good:
hash_t MCHashPointer(void *p_pointer);
-
Whenever adding a function, add a comment that explains precisely:
- what the function does, under what conditions it succeeds, and how it behaves when it fails
- what the function expects as inputs, and what outputs it generates
-
Types declared with
structmust be Plain Old Data (POD). -
All composite types should have a default constructor.
-
All constructors should initialise all fields, preferably in an initialiser list rather than in the constructor body.
-
Use a bit field when declaring boolean values in a
structorclass:bool m_bool_member : 1 -
Avoid including minimum, maximum or default members in an
enum. Consider:- declaring typed constants alongside the enumeration
- overloading
std::begin()andstd::end()for the enumeration
-
Use "managed lifetime" types wherever possible. If
std::unique_ptrorstd::shared_ptrare not suitable, use theMCAutotypes fromfoundation-auto.h.Good (automatic cleanup):
MCAutoStringRef t_unicode; if (!MCStringUnicodeCopy(p_input, &t_unicode)); return false; return MCDoSomethingWithUnicode(*t_unicode);Bad (explicit cleanup):
MCStringRef t_unicode; if (!MCStringUnicodeCopy(p_input, t_unicode)) return false; auto t_result = MCDoSomethingWithUnicode(*t_unicode); MCValueRelease(t_unicode); return t_result; -
When class instances "own" instances of other types, use "managed lifetime" types as the class members.
-
As a rule, avoid writing code that requires explicit
delete,delete[],free(),MCValueRelease(),MCMemoryDeallocate(), etc. If possible, also avoid explicit allocation -
Always check the success of memory allocations. If the calling code can't handle memory allocation failure, prefix the allocating statement with
/*UNCHECKED*/. -
When using the
newoperator, always either use non-allocating placement allocation ("placement new"), or specifystd::nothrow:int *t_array = new (nothrow) int[10];
-
Always initialise local variables at the point of declaration.
-
Initialise POD types using
=:bool t_success = true; -
Initialise non-POD types using construction syntax:
MCSpan<byte_t>(p_pointer, p_length); -
In the absence of another suitable value, initialise:
- pointers to
nullptr boolvalues totrueorfalse- numbers to
0
- pointers to
-
-
Use the
autotype specifier when:- the full type is irrelevant
- the full type is explicitly stated on the line
- writing templated code (e.g. implementing
min()/max())
-
Functions must not modify "out" parameters until immediately before returning, and only on success.
-
gotois usually only acceptable for cleaning up on errors; try to use managed lifetime types instead. -
When disabling code with an always-false condition, prefix the condition with
/* DISABLES CODE */:if (/* DISABLES CODE */ (false)) { /* statements */ }
-
Declare all compile time constants as fully
const. This is particularly important for data or lookup tables. -
Only pass
boolvalues to conditions (e.g.if,while, etc.) Do not assume thatnullptror0are false. -
Never use C-style casts. Use:
- Construction, if possible
reinterpret_castfor bit-casts (e.g. converting a pointer to an integer or vice versa).dynamic_castto cast aclassto or from a derivedclass.static_castfor other types of type cast
Avoid using
const_cast. -
Never pass a pointer and length as separate arguments to a function. Use an
MCSpaninstead. -
Avoid using preprocessor macros to abbreviate code; use
inlinefunctions or templates instead. -
Avoid using the ternary
/*condition*/ ? /*if_true*/ : /*if_false*/operator. You may need to use one when initialising aconstvalue. However, when the condition or the true/false values require large expressions, or when there are more than two possible values, an immediately-invoked function expression can do the job more clearly:const auto&& t_value = [&, this]() { if (/*condition*/) { return /* compute value if true */; } else { return /* compute value if false */; })(); -
When writing lambda expressions, try to avoid return type specification; use inference or, if necessary, cast or construct a value in the
returnstatement.auto t_valid_handle = [](const ObjectHandle& p_obj) { return bool{p_obj}; }
-
Use spaces for indentation and alignment, with 4 spaces per level of indentation.
-
Avoid lines over 80 characters in length. When wrapping expressions, across multiple lines, try to place a line break after a binary logical operator (
&&,||) or after a comma. -
Use only one statement per line.
-
Use single blank lines to separate areas of code within a function, and to separate function and type definitions.
-
All curly braces should be on a line on their own, indented to match the level of the construct they relate to.
Good:
if (/* condition */) { /* statements */ }Bad:
if (/* condition */) { /* statements */ } -
Use a single space after the
for,while,ifandswitchkeywords, as in the example above. -
Do not insert a space between a function or macro name and its parameter list.
Good:
void main(int argc, char**argv);Bad:
void main (int argc, char **argv); -
Place a space or newline after each comma, as in the example above.
-
Place a space before and after binary operator.
Good:
bool t_okay = (t_left == t_right);Bad:
double t_area = 2*acos(0.0)*t_radius*t_radius; -
When using preprocessor macros, ensure that the
#defined identifier has a value.Good:
#define FEATURE_PLATFORM_PLAYER (1)Bad:
#define FEATURE_PLATFORM_PLAYER