forked from Alepha/Alepha
Move some docs to a docs dir.
This lets me have a dir named `Format`, which I want.
This commit is contained in:
388
docs/Format
Normal file
388
docs/Format
Normal file
@ -0,0 +1,388 @@
|
||||
Basic Code Formatting Rules:
|
||||
|
||||
0) Like most things in Alepha, these rules are "strong guidelines". When in doubt, these rules
|
||||
form a baseline of good current practice. However, there's no substitute for good judgement.
|
||||
Therefore, a large degree of formatting latitude is afforded to code. The primary value
|
||||
being sought is clarity, not consistency. Therefore, new stylistic forms are constantly being
|
||||
adopted for specific cases -- it is cumbersome (at best) to keep track of all of them here.
|
||||
|
||||
Reason: Format should convey meaning. It's fine to invent new conventions. Documenting them is
|
||||
good too, but it shouldn't be cumbersome to do so -- thus this file itself will always be an
|
||||
incomplete representation of the format.
|
||||
|
||||
Exceptions: Certain core guidelines are universally required, like tabs.
|
||||
|
||||
1) Alepha code uses tab character ('\t', ASCII:0x09) for indentation. One tab per indent level. Tabs
|
||||
are not to be used for alignment, except with other tabs. Tabs have no expected set width.
|
||||
|
||||
Reason: Tabs permit customization of code indent level to a reader's taste via the tabwidth settings
|
||||
of editors.
|
||||
|
||||
Exceptions: None. The tab character as _indent only_ permits cooperation amongst those with different
|
||||
visual preferences.
|
||||
|
||||
2) Semicolons on all lines shall appear immediately after the last syntactic character, without a separating space,
|
||||
immediately followed by a newline (or trailing comment). It is permissible put a semicolon on its own line to
|
||||
facilitate a block structure where appropriate for the language.
|
||||
|
||||
Examples:
|
||||
|
||||
++i;
|
||||
f( a, b );
|
||||
|
||||
const bool value= true
|
||||
or a
|
||||
or b
|
||||
;
|
||||
|
||||
3) Declare only one variable per "line".
|
||||
|
||||
Examples:
|
||||
|
||||
int x;
|
||||
float *y;
|
||||
int &xr= x;
|
||||
|
||||
Reason: C++ declaration grammar inherits ambiguities and oddities from C. By declaring one variable per line, we sidestep this issue.
|
||||
|
||||
Exceptions: None.
|
||||
|
||||
|
||||
4) All bracing constructs, "(), {}, [], and <>", must have a space between the opening brace and the first
|
||||
argument, and between the last argument and the closing brace. If there are no arguments then there should
|
||||
be no spaces between the opening and closing brace. No space shall exist between a keyword or an entity name
|
||||
and the following opening bracing construct. A newline character counts as a space for these constructs,
|
||||
thus permitting block-like structure.
|
||||
|
||||
Examples:
|
||||
|
||||
if( someFunction( argument ) ) return 1; // Good. Notice that there is no trailing space after the "if"
|
||||
std::vector< std::map< std::string, std::set< std::string > > > complexDataEntity; // Even for templates
|
||||
complexDataEntity[ 10 ][ "Hello "].find( "Something" ); // Even for array-like operations.
|
||||
std::cout << [](){ return "Hello World!"; }() << std::endl; // For one-lining lambdas and initializers, the rule applies.
|
||||
for( int i= 0; i < 10; ++i ) std::cout << i << std::endl; // Even within a loop construct.
|
||||
|
||||
Reason: Extra space can be visually helpful. Many styles encourage visual cues to code flow.
|
||||
|
||||
|
||||
5) All 2-argument operators that do not modify the left hand side (binary-function operators) must have a space
|
||||
on both sides of the operator, whereas data-modifying equals-based operators like ' =, +=, -=, *=, /=, ^=, |=,
|
||||
&=, ~=, <<=, >>=, %= ' shall have no space to their left, thus binding them to the expression that they modify.
|
||||
Note that ' !=, <=, >= ' are not assignment-like operators, but a comparison operators. Comma "operators"
|
||||
shall also follow left-hand no spacing rules, as this matches their natural use in other circumstances.
|
||||
|
||||
Examples:
|
||||
|
||||
int a= 10;
|
||||
int b= 11, c= 12;
|
||||
c= ( a * b + c ) / ( a * 22 );
|
||||
a%= c / b;
|
||||
if( ( a != b ) or ( c < a ) && ( a= b ) ) return true;
|
||||
|
||||
Reason: Extra visual space helps. Cuddling the assignment operators helps distinguish them from comparison
|
||||
operators. "<=" vs "<<=" and "==" vs "=". By having extra code-intent presented in a side-channel (spacing),
|
||||
it helps prevent accidental typos and bugs.
|
||||
|
||||
Exceptions: `<=` when used as a "left fat arrow" operator may be used with the spacing rules that most
|
||||
make sense for their context. `<=[]` on lambdas, for example. `/` when used as a directory separator
|
||||
between path components from `std::filesystem` should also omit surrounding spaces.
|
||||
|
||||
|
||||
6) All unary operators shall "cuddle" their applied variable. Pointers and references shall be be declared
|
||||
with the "&" or "*" attached to the variable name.
|
||||
|
||||
Examples:
|
||||
|
||||
int a= 10;
|
||||
++a;
|
||||
a--;
|
||||
a= ~a;
|
||||
if( !a ) return false;
|
||||
|
||||
Reason: Somewhat improves readability. For declarations, although "int &" is the type of "int &x;", this
|
||||
does not extend to: "int &x, y". Those are distinct types, but by banning multiple declarations per line,
|
||||
we mostly avoid this issue.
|
||||
|
||||
|
||||
7) Brace placement shall be "Allman/BSD"-like. Opening braces shall be on a new line, closing braces on a
|
||||
new line. Braces shall be indented by the same level as the line of their "control" statement. A braced-block
|
||||
that is not subordinate to a control statement shall be indented as if it had an "if( true )" control statement
|
||||
above it. A do-while loop shall have the while on its own line, after the closing brace, where possible. Note
|
||||
that one-line compaction is permitted, as is omitting braces for fully subsumed blocks.
|
||||
|
||||
Examples:
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
int x= 10;
|
||||
int y= 11;
|
||||
if( x < y )
|
||||
{
|
||||
std::vector< int > v;
|
||||
if( y % 2 )
|
||||
{
|
||||
rv.push_back( v );
|
||||
}
|
||||
|
||||
// Note that this if block has no parent, but it is indented like it has one.
|
||||
{
|
||||
std::list< int > l( v.begin(), v.end() );
|
||||
if( x == y ) for( const auto &x: stuff )
|
||||
{
|
||||
std::cout << x << std::endl;
|
||||
}
|
||||
std::cout << l.size() << std::endl;;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
std::cout << v.size() << std::endl;
|
||||
}
|
||||
while( false );
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Exceptions: Very small functions or blocks can be subsumed into one line, for brevity.
|
||||
|
||||
8) Return types shall be placed above the name of the function in definitions, but on the same line for
|
||||
declarations. inline or explicit for ctors, dtors, and conversion functions shall follow this rule as well.
|
||||
|
||||
Example:
|
||||
|
||||
bool is_even( int x );
|
||||
|
||||
bool
|
||||
is_even( int x )
|
||||
{
|
||||
return ~x & 1;
|
||||
}
|
||||
|
||||
|
||||
9) Template clauses for definitions shall be on their own line. For declarations, a separate line is optional.
|
||||
|
||||
Examples:
|
||||
|
||||
template< typename T > bool is_even( T x );
|
||||
|
||||
template< typename T >
|
||||
bool
|
||||
is_even( T x )
|
||||
{
|
||||
return ( x % 2 ) == 0;
|
||||
}
|
||||
|
||||
|
||||
10) In class definitions, public, protected, and private regions shall be indented beyond the access control
|
||||
keywords, which must also be indented. In switch/case blocks, cases shall be similarly single-indented,
|
||||
as too breaks shall remain single indented. All switch statements shall have a break, return, or throw
|
||||
at the end of each case clause or shall use [[fallthrough]] to document fallthrough behavior, where
|
||||
it is used. Default clauses are not mandatory, but are recommended. (Default clauses might be blank,
|
||||
or all cases might be covered, especially when the argument is an enum.)
|
||||
|
||||
Examples:
|
||||
|
||||
class Foobar
|
||||
{
|
||||
private:
|
||||
int x;
|
||||
|
||||
public:
|
||||
void setX( int newx ) { x= newx; }
|
||||
int getX() const { return x }
|
||||
|
||||
void
|
||||
operation( int val )
|
||||
{
|
||||
switch( val )
|
||||
{
|
||||
case 2:
|
||||
x= 11;
|
||||
break;
|
||||
|
||||
case 11:
|
||||
x= 2;
|
||||
break;
|
||||
|
||||
case 12:
|
||||
specialCase();
|
||||
[[fallthrough]];
|
||||
|
||||
default:
|
||||
x= val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct Open
|
||||
{
|
||||
int x;
|
||||
};
|
||||
|
||||
|
||||
11) Continued lines shall be indented twice past the main parent's indent. When breaking a line on an operator,
|
||||
the next line should begin with an operator. Spaces may also be used to line up nicely for vertical alignment
|
||||
of operators; however, note that spacing alignment starts from the tab-depth of the previous line. (Never use
|
||||
spaces to align to a specific tab depth.) Control structures which have a single subordinate statement
|
||||
on a separate line shall enclose that line in braces. If wrapping becomes necessary with "one line" control
|
||||
structures, then bracing is required.
|
||||
|
||||
Example:
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
std::cout << "A really long message which "
|
||||
<< "needed to be indented "
|
||||
<< "across multiple lines." << std::endl;
|
||||
|
||||
std::cout << "A really long message which "
|
||||
<< "needed to be indented "
|
||||
<< "across multiple lines. "
|
||||
<< "This message was aligned "
|
||||
<< "on ostream operators."
|
||||
<< std::endl;
|
||||
|
||||
if( condition ) action();
|
||||
|
||||
if( moreComplicatedCondition )
|
||||
{
|
||||
moreComplicatedAction();
|
||||
}
|
||||
}
|
||||
|
||||
Reasons: It is important to know when lines have wrapped. A specific visually distinct
|
||||
style helps make this stand out from other indented scenarios. By avoiding braceless
|
||||
subordinates to control structures we avoid a class of silly maintenance bugs.
|
||||
|
||||
|
||||
12) Initializer lists shall be treated as-if they're part of the ctor body. They get
|
||||
single indented, unless the function is really short.
|
||||
|
||||
Example:
|
||||
|
||||
struct StringWrapper
|
||||
{
|
||||
std::string string;
|
||||
|
||||
explicit
|
||||
StringWrapper( const std::string &s )
|
||||
: string( s ) {}
|
||||
|
||||
explicit StringWrapper() : string( "no value" ) {} // A short ctor or other function can be on one-line.
|
||||
};
|
||||
|
||||
Exceptions: Single line functions and ctors, and the like are sometimes good to conserve space. Trivial
|
||||
implementations should take trivial amounts of space.
|
||||
|
||||
|
||||
13) Private data shall be declared before the functionality of the class is declared or defined. Constructors,
|
||||
copy assignment, and destructors shall be declared (or defined) before other member functions.
|
||||
|
||||
Example:
|
||||
|
||||
template< typename T >
|
||||
class smart_ptr : boost::noncopyable
|
||||
{
|
||||
private:
|
||||
T *ptr;
|
||||
|
||||
public:
|
||||
explicit inline
|
||||
smart_ptr( T *const p )
|
||||
: ptr( p ) {}
|
||||
|
||||
inline ~smart_ptr() { delete p; }
|
||||
|
||||
T &
|
||||
operator *() const
|
||||
{
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
T *
|
||||
operator->() const
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
};
|
||||
|
||||
Reasons: It's easier to reason about the correctness of a class's primary invariant mechanism (construction
|
||||
and destruction) when its near the variables and types it manages. Knowing the contents of a class helps to
|
||||
understand about the invariants which must be maintained. Some argue this makes finding the public interfaces
|
||||
harder -- but Hera classes are designed to be perused by doxygen, and thus this documentation shall serve as
|
||||
a primary public usage reference. Within Hera, the only reason to read a header file is to understand more
|
||||
about a particular implementation.
|
||||
|
||||
Exceptions: None.
|
||||
|
||||
|
||||
14) Class parents (inheritance) shall logically be on the same line as the declaration. If on another line,
|
||||
must count as a continuation.
|
||||
|
||||
Reasons: It helps to isolate a class's name from the parent list, which is (when you think about it) part of
|
||||
the class, and needs indentation.
|
||||
|
||||
|
||||
15) Preprocessor nested scopes (which sometimes do happen) shall indent, by requisite spacing, all characters
|
||||
AFTER the leading "#".
|
||||
|
||||
Examples:
|
||||
|
||||
#ifndef SOME_THING
|
||||
# ifndef SOME_OTHER_THING
|
||||
# define NEITHER_THING_IS_AVAILABLE
|
||||
# else
|
||||
# error Must have SOME_THING if SOME_OTHER_THING is around
|
||||
# endif
|
||||
#endif
|
||||
|
||||
Reasons: Because C preprocessor macros also follow logical scope, it helps to trace their nesting by indentation.
|
||||
By indenting after the leading "#", we help distinguish C++ code from C preprocessor code.
|
||||
|
||||
Exceptions: The header guards, being ubiquitous, shall not count as a level of indentation in nested preprocessor
|
||||
construct
|
||||
|
||||
|
||||
16) In declaration of entities, the following order of keywords shall always be followed:
|
||||
|
||||
virtual explicit extern static constexpr consteval constinit inline mutable const volatile <TYPE>
|
||||
|
||||
For integers & double:
|
||||
unsigned long long int
|
||||
signed short int
|
||||
long double
|
||||
|
||||
signed and int are each optional.
|
||||
|
||||
For characters:
|
||||
unsigned char
|
||||
signed char
|
||||
char
|
||||
|
||||
Each of those char types are three distinct types -- signed or unsigned are NOT optional.
|
||||
|
||||
|
||||
Examples:
|
||||
struct Type
|
||||
{
|
||||
virtual inline ~Type() noexcept {} // Yes, this is actually allowed.
|
||||
explicit inline Type() {}
|
||||
static inline const std::string string() { return "String"; }
|
||||
|
||||
// Allowed, but violates the style guide:
|
||||
long const unsigned volatile int virtual long inline *resource() const;
|
||||
|
||||
// The same declaration, following the style guide:
|
||||
virtual inline const volatile unsigned long long int *resource() const;
|
||||
};
|
||||
|
||||
Reasons: C++ is very forgiving in the order of its type-and-qualifiers, but standardizing them makes finding
|
||||
specific things simpler. There are many mutually exclusive keywords above, like static and virtual and explicit,
|
||||
for example. Many may not know it but 'long const unsigned volatile int virtual long inline *' is a valid token
|
||||
sequence for declaring a member function. Standardization gives us: 'virtual inline const volatile unsigned
|
||||
long long int *', which makes the declaration type far less confusing.
|
18
docs/README.doxygen
Normal file
18
docs/README.doxygen
Normal file
@ -0,0 +1,18 @@
|
||||
To generate a doxygen reference for Alepha, run "doxygen doxyfile.alepha"
|
||||
|
||||
A subfolder, DoxygenAlepha, will be created
|
||||
|
||||
Point a browser to the html/index.html within DoxyAlepha to bring up the main page.
|
||||
|
||||
The .gitignore file here excludes the DoxyAlepha subfolder in its entirety.
|
||||
|
||||
If there are too many namespaces in the Class List, consider using Class Index instead.
|
||||
|
||||
Note:
|
||||
Upgraded to version 1.10.0 to, hopefully get better c++20, including concepts
|
||||
|
||||
Todo:
|
||||
- create a script to execute doxygen
|
||||
- as part of that script preprocess source file (c++ and header) to remove or simplify namespaces
|
||||
- set the project brief and project logo in doxygen config
|
||||
- customize header and footer
|
223
docs/Style
Normal file
223
docs/Style
Normal file
@ -0,0 +1,223 @@
|
||||
Alepha library C++ style Guide
|
||||
|
||||
This guide shall follow the format of "Rule", then "Examples", then "Reasoning", then "Exceptions".
|
||||
Some of these clauses are omitted. No portion of the C++ language itself is off-limits, unless explicit mention
|
||||
is made banning that feature -- which is rare. The Alepha C++ philosophy sees C++ as a whole langauge and
|
||||
does not seek to provide a "safe subset". The few banned features tend to be those which are either deprecated
|
||||
or generally unused.
|
||||
|
||||
Note that this "style guide" covers the style of the code in the sense of how to write the code itself,
|
||||
not the rendering/format. For that guide, please see the "Format" file.
|
||||
|
||||
General Coding and Language Rules:
|
||||
|
||||
0. The Alepha C++ library shall only contain code written for ISO/ANSI C++23, compilation with any other language
|
||||
standard is incidental. Modifying a component of Alepha to compile with a non-compliant compiler (while still
|
||||
remaining compliant to the standard) is sometimes necessary. Deprecated features should be avoided, wherever
|
||||
possible. C++98 code (with precious few exceptions) will work unchanged in C++23.
|
||||
|
||||
Reason: Alepha is a modern C++ library technology.
|
||||
|
||||
Exceptions: None. ("#pragma once" is used, as if it were part of the standard. This is widely supported,
|
||||
and when C++20's modules become more prevalent, the library will move to those.)
|
||||
|
||||
Note: Even among all "compliant" compilers, some quirks prevent perfect, and ideal C++23 code.
|
||||
Even in such cases, the code which does compile is still expected to be C++23 compliant.
|
||||
|
||||
|
||||
1. All names and user-defined tokens shall avoid C89, C99, C11, C++98, C++03, C++11, C++14, C++17, C++20, and C++23
|
||||
implementation reserved names, in all contexts.
|
||||
|
||||
- Names shall not begin with an underscore.
|
||||
- Names shall not contain two consecutive underscores.
|
||||
- User defined names include: functions, macros, classes, constants, and header guards.
|
||||
- Be mindful of C++'s little known reserved words: compl or not and xor bit_and bit_or bit_xor
|
||||
|
||||
Reason: Alepha is a portable C++ library, and thus must be able to compile with any C++23 compiler. The presence
|
||||
of implementation reserved names in code can cause potential issues. Even in a context where the name wouldn't
|
||||
be reserved, consider it to be reserved.
|
||||
|
||||
Exceptions: NONE
|
||||
|
||||
|
||||
2. `#define`s of a keyword to anything else are prohibited.
|
||||
|
||||
Reason: #defining a keyword yields non-portable behavior. On a fully conforming C++23 compiler, all features
|
||||
are present, and useful. Not all compilers behave in the same way, when it comes to violating the standard.
|
||||
|
||||
Exceptions: None. For a while, it was necessary to supplement some compilers with `#define override` or
|
||||
similar hacks, for not-yet-supported C++11 features. By this point in time, the new features in C++20 and
|
||||
C++23 cannot be so easily "hacked in".
|
||||
|
||||
|
||||
3. No C++23 core-language features shall be proscribed from use, with the exception of avoiding the following
|
||||
idioms or features (some are banned outright):
|
||||
|
||||
- digraphs are forbidden (Trigraphs were removed in 17)
|
||||
- prefer `new` over `malloc` (except in implementing `::operator new`)
|
||||
- prefer to use `()` instead of `( void )` in empty parameter functions
|
||||
- avoid `reinterpret_cast` (except in specific hardware-oriented contexts, or where otherwise necessary)
|
||||
- rarely use "old C style" casts. (E.g.: `(char *) variable`) -- this is widely known to be dangerous
|
||||
- never throw from a destructor -- this is not expected by any C++23 libraries
|
||||
- Avoid the use of general `mutable` members. No good can come from changing values behind const, even locks.
|
||||
TOCTIOU bugs abound with observers of threadsafe mechanisms. Properly written Atomics primitives still need
|
||||
to be members of classes as mutable.
|
||||
|
||||
Reason: C++23 is a cohesive, well-reasoned language with many tightly-integrated features. Circumscription of
|
||||
a feature is ill-advised. The above prohibitions are mostly encouraged by choices of the standards committee
|
||||
over the past 20 or more years. Reinterpret cast, in particular, represents a standards-sanctioned form of
|
||||
platform specific behavior. Digraphs and trigraphs were a compatibility feature required by the standard, but
|
||||
are bizarre and unintuitive -- many compilers disabled them unless in strict compatibility modes, and they are
|
||||
removed in C++23.
|
||||
|
||||
Exceptions: Malloc can be used in circumstances where that allocator is necessary. "( void )" parameter lists are
|
||||
usable in specific 'meant to be included in C' headers -- these must be in their own directory. C style casts
|
||||
are dangerous, in general, but '(float) 4 / 2' is more readable than: 'static_cast< float >( 4 ) /
|
||||
static_cast< float >( 2 )' or 'std::divide< float >{}( 4, 2 )' -- prefer function-style casting if possible though.
|
||||
Never cast with typedefs, or pointers. Destructor throwing is banned, because no C++ STL library can recover
|
||||
from it, nor can the exception system itself. Reinterpret_cast has platform dependent behavior, and should
|
||||
only be in platform-dependent code. Digraphs should only exist in obfuscation contests. Assume that
|
||||
destructors that throw always cause undefined behavior.
|
||||
|
||||
|
||||
4. No non-portable, undefined, implementation defined, or optional behaviors, functions and features shall be
|
||||
placed into any non-detail portions of the Alepha library. Detail portions shall push implementation-specifics
|
||||
into isolated files. Only the "Alepha::Windows", "Alepha::Posix", or similar namespaces shall contain any
|
||||
non-portable constructs.
|
||||
|
||||
Reasons: Portability is a top concern for the Alepha library.
|
||||
|
||||
Exceptions: Alepha has namespaces dedicated to specific platforms and systems -- those namespaces are suitable
|
||||
for non-portable code, but no core library portions may rely upon those. (Code must compile with a C++11
|
||||
compiler and be platform agnostic.) For non-portable namespaces, the code should remain compiler agnostic,
|
||||
where possible. Always use macro-guards to prevent accidental compilation of files intended for only one compiler.
|
||||
|
||||
|
||||
5. Avoid "pass-by-non-const-reference" for "out-params". Prefer tuples, pairs and aggregate types for multiple
|
||||
return values, and use exceptions to signal failures.
|
||||
|
||||
Reason: "push-to-argument" style was useful in the 1990s when compilers were very bad at optimizing the
|
||||
class return path. In a post-C++11 world, with both NRV and move semantics, this caveat is no longer pertinent.
|
||||
Further, this out-parameter style was necessary when exceptions were avoided, as returning an aggregate is
|
||||
not conducive to error-checking. Alepha fully embraces the C++ exceptions mindset. Exceptions exist in the
|
||||
language to signal out-of-band errors -- return codes are to be avodied.
|
||||
|
||||
Exceptions: Sometimes arguments have to be passed to be modified, like 'operator <<' on std::ostream. The
|
||||
'operator >>' std::istream operators are also examples of this, but technically fall under the "out-params" rule.
|
||||
Historically these iostream constructs exist, and we shall still support the iostreams idiom -- it's part of
|
||||
the standard.
|
||||
|
||||
|
||||
6. Manage all resources with objects and RAII. Prefer to manage only a single resource per RAII class. Do not
|
||||
ever allow unshepherded or unowned resources to exist. Directly callling "new" is strongly discouraged.
|
||||
|
||||
Reason: It's not just a good idea -- all of C++23 is built around the RAII idiom. It's necessary for exception
|
||||
safety, which is a core part of the Alepha philosophy. An RAII class which has to manage several resources
|
||||
often represents a design bug. Some classes (like ref-counting-pointer constructs) may need to allocate some
|
||||
resources to manage another resource -- this is unavoidable, but the other resources are management metadata.
|
||||
|
||||
Exceptions: None, whatsoever.
|
||||
|
||||
|
||||
7. Unless a class is intended to directly manage a resource, it ought to have a blank destructor, in the
|
||||
ideal situation. Prefer `AutoRAII` as the basis of a class designed to use resource management, in the general
|
||||
case.
|
||||
|
||||
Reason: RAII is most effective when each class that actually manages a resource only manages a single resource,
|
||||
and concept abstraction classes don't have to have any explicit resource management.
|
||||
|
||||
Exceptions: Sometimes a destructor of a class may need to call one of its own functions, like a "cancel"
|
||||
function. A network request, or a thread, for example. This is not entirely an exception, since that class
|
||||
models that concept as a kind of resource. Conversely, those "cancel" functions are merely an exposition of
|
||||
the dtor functionality for early voluntary cleanup.
|
||||
|
||||
|
||||
8. Naked `delete` and other resource release calls outside of destructors are banned. Naked `new` and other
|
||||
resource allocation calls that are assigned to raw unmanaged C types, outside of constructors or constructor
|
||||
parameters are banned. This rule works hand-in-hand with the previous three rules. Prefer scoped_ptr,
|
||||
unique_ptr, shared_ptr, weak_ptr, AutoRAII, vector, list, set, map, and other types over rolling your own
|
||||
resource management types, where possible. Calls to 'new' and 'delete' are bad code smells. Use of raw
|
||||
primitive pointers is also such a code smell. Consider using `single_ptr` or a native reference when working
|
||||
with items held at a distance. Alepha::single_ptr is only constructible from an owned resource, or another
|
||||
single_ptr.
|
||||
|
||||
Reason: C++ RAII is the only safe way to manage resources. By limiting the resource release code to only
|
||||
exist in dtors, it limits the scope of code needed to audit for leak-safety. Even RAII classes should only
|
||||
handle resource release through the dtor path. Resource release outside of this path should be viewed as a
|
||||
glaring bug. Resource acquisition outside of a ctor, or an argument to a ctor should likewise be seen as a bug.
|
||||
Raw C types are unsafe for resource management in the face of changing resources.
|
||||
|
||||
Exceptions: None. Although passing a lambda calling a release function to an AutoRAII object is technically
|
||||
an exception, it should be thought of as writing an inline dtor. Note that std::make_unique and
|
||||
std::make_shared are suitable replacements for new in almost all situations.
|
||||
|
||||
|
||||
9. Avoid the use of #ifdef/else/endif for conditional compilation of code -- prefer to include headers with
|
||||
well defined code for these purposes.
|
||||
|
||||
Reason: Conditional compilation makes code multiply architecturally dependent not independent.
|
||||
|
||||
Exceptions: Some small blocks of code (particularly preprocessor control macros) can be if-else-endif for
|
||||
small specific variances.
|
||||
|
||||
|
||||
|
||||
|
||||
Basic Environment Rules:
|
||||
|
||||
0. Alepha shall compile on any C++23 compliant compilers. At present gcc-13 is a supported minimum,
|
||||
and is also the "reference compiler".
|
||||
|
||||
Exception: Compiler specific code in detail sections need only work with that compiler. Platform specific code,
|
||||
in Alepha::<Platform> namespaces need only work on that target platform, but should be portable across compilers
|
||||
for that platform. Alepha::Posix sections should try to also work in Cygwin, where possible.
|
||||
|
||||
1. Header files shall be named with a trailing ".h". Headers shall be entirely compatible with C++.
|
||||
|
||||
2. Header files which are safe to include from "C" language parsers shall be organized into a specific
|
||||
"c_interface" subdirectory of the appropriate domain. Such headers are rarely expected.
|
||||
|
||||
|
||||
Basic Naming Rules:
|
||||
|
||||
0. Use good judgement in naming things. This cannot be codified as a set of mechanical rules.
|
||||
|
||||
1. Follow this general naming style (there's some room for flexibility within):
|
||||
|
||||
* ClassesAndTypedefsLikeThis
|
||||
* PublicNamespaceAlsoLikeThis
|
||||
* functionsAndVariablesLikeThis
|
||||
* ALEPHA_PATH_TO_MACROS_GET_NAMED_IN_VERY_LONG_STYLE_AND_OBNOXIOUS_WITH_CAPS_AND_UNDERSCORES
|
||||
* meta_programming_things_like_this
|
||||
* TCP_IP_Socket or connectToTCP_IP_Socket-- If the entity name contains abbreviations, separate the abbreviation by `_`,
|
||||
don't case flatten things like TCP to Tcp.
|
||||
* Follow STL and related C++ Standards names, where appropriate (`push_back` not `pushBack`).
|
||||
|
||||
2. Follow the general module-namespace technique defined in the NAMESPACES document.
|
||||
|
||||
3. Name the private module namespace within files as ` ::detail:: FileName_m`, such that `FileName.h`
|
||||
provides the namespace. This provides a simple transform:
|
||||
Alepha/IOStream/String.h -> Alepha::IOStream::String_m
|
||||
|
||||
4. General file naming is case-sensitive. Every major modern operating system supports a case-sensitive
|
||||
filesystem. Use it. Windows' default filesystem is case-preserving. This case-preserving property
|
||||
should suffice for most file naming situations; however, if `foo.h` and `Foo.h` both exist, it might
|
||||
cause a problem. That problem is more easily remedied by using a case-sensitive filesystem than by
|
||||
putting an onus for name mangling onto a project.
|
||||
|
||||
5. Name files after the primary component in them, if the file makes a single component available.
|
||||
|
||||
Example: `class Alepha::Foobar::Quux` should be defined in `Alepha/Foobar/Quux.h`, if defined in a single
|
||||
file. The full public name of that class would be `Alepha::Foobar::exports::Quux_m::Quux`. The private
|
||||
name can be anything, of course, but would typically be `Alepha::Foobar::detail::Quux_m::exports::Quux`.
|
||||
|
||||
6. Name files which provide families of facilities without leading capitals. Those names shouldn't be
|
||||
confused for classes.
|
||||
|
||||
7. Name functions with a verb, where appropriate. Don't name observers with a `get` verb.
|
||||
|
||||
8. Avoid names with `do`, `run`, `compute`, or `execute` in them for functions. Remember functions
|
||||
`do`, they aren't things.
|
||||
|
||||
9. Avoid names with `-er`, `Manager`, `Owner`, or `Holder` in them for classes. Remember that
|
||||
classes don't `do`, they're not functions.
|
Reference in New Issue
Block a user