CPL: C-style programming

C and C++ are closely related languages. They both originate in “Classic C” from 1978 and have evolved in ISO committees since then. Many attempts have been made to keep them compatible, but neither is a subset of the other.

C rule summary:

CPL.1: Prefer C++ to C

Reason

C++ provides better type checking and more notational support. It provides better support for high-level programming and often generates faster code.

Example
char ch = 7;
void* pv = 
&
ch;
int* pi = pv;   // not C++
*pi = 999;      // overwrite sizeof(int) bytes near 
&
ch

The rules for implicit casting to and fromvoid*in C are subtle and unenforced. In particular, this example violates a rule against converting to a type with stricter alignment.

Enforcement

Use a C++ compiler.

CPL.2: If you must use C, use the common subset of C and C++, and compile the C code as C++

Reason

That subset can be compiled with both C and C++ compilers, and when compiled as C++ is better type checked than “pure C.”

Example
int* p1 = malloc(10 * sizeof(int));                      // not C++
int* p2 = static_cast
<
int*
>
(malloc(10 * sizeof(int)));   // not C, C-style C++
int* p3 = new int[10];                                   // not C
int* p4 = (int*) malloc(10 * sizeof(int));               // both C and C++
Enforcement
  • Flag if using a build mode that compiles code as C.

    • The C++ compiler will enforce that the code is valid C++ unless you use C extension options.

CPL.3: If you must use C for interfaces, use C++ in the calling code using such interfaces

Reason

C++ is more expressive than C and offers better support for many types of programming.

Example

For example, to use a 3rd party C library or C systems interface, define the low-level interface in the common subset of C and C++ for better type checking. Whenever possible encapsulate the low-level interface in an interface that follows the C++ guidelines (for better abstraction, memory safety, and resource safety) and use that C++ interface in C++ code.

Example

You can call C from C++:

// in C:
double sqrt(double);

// in C++:
extern "C" double sqrt(double);

sqrt(2);
Example

You can call C++ from C:

// in C:
X call_f(struct Y*, int);

// in C++:
extern "C" X call_f(Y* p, int i)
{
    return p-
>
f(i);   // possibly a virtual function call
}
Enforcement

None needed

SF: Source files

Distinguish between declarations (used as interfaces) and definitions (used as implementations). Use header files to represent interfaces and to emphasize logical structure.

Source file rule summary:

SF.1: Use a.cppsuffix for code files and.hfor interface files if your project doesn’t already follow another convention

Reason

It’s a longstanding convention. But consistency is more important, so if your project uses something else, follow that.

Note

This convention reflects a common use pattern: Headers are more often shared with C to compile as both C++ and C, which typically uses.h, and it’s easier to name all headers.hinstead of having different extensions for just those headers that are intended to be shared with C. On the other hand, implementation files are rarely shared with C and so should typically be distinguished from.cfiles, so it’s normally best to name all C++ implementation files something else (such as.cpp).

The specific names.hand.cppare not required (just recommended as a default) and other names are in widespread use. Examples are.hh,.C, and.cxx. Use such names equivalently. In this document, we refer to.hand.cppas a shorthand for header and implementation files, even though the actual extension may be different.

Your IDE (if you use one) may have strong opinions about suffices.

Example
// foo.h:
extern int a;   // a declaration
extern void foo();

// foo.cpp:
int a;   // a definition
void foo() { ++a; }

foo.hprovides the interface tofoo.cpp. Global variables are best avoided.

Example, bad
// foo.h:
int a;   // a definition
void foo() { ++a; }

#include <foo.h>twice in a program and you get a linker error for two one-definition-rule violations.

Enforcement
  • Flag non-conventional file names.
  • Check that .h and .cpp (and equivalents) follow the rules below.

SF.2: A.hfile may not contain object definitions or non-inline function definitions

Reason

Including entities subject to the one-definition rule leads to linkage errors.

Example
// file.h:
namespace Foo {
    int x = 7;
    int xx() { return x+x; }
}

// file1.cpp:
#include 
<
file.h
>

// ... more ...

 // file2.cpp:
#include 
<
file.h
>

// ... more ...

Linkingfile1.cppandfile2.cppwill give two linker errors.

Alternative formulation: A.hfile must contain only:

  • #include s of other .h files (possibly with include guards)
  • templates
  • class definitions
  • function declarations
  • extern declarations
  • inline function definitions
  • constexpr definitions
  • const definitions
  • using alias definitions
  • ???
Enforcement

Check the positive list above.

SF.3: Use.hfiles for all declarations used in multiple source files

Reason

Maintainability. Readability.

Example, bad
// bar.cpp:
void bar() { cout 
<
<
 "bar\n"; }

// foo.cpp:
extern void bar();
void foo() { bar(); }

A maintainer ofbarcannot find all declarations ofbarif its type needs changing. The user ofbarcannot know if the interface used is complete and correct. At best, error messages come (late) from the linker.

Enforcement
  • Flag declarations of entities in other source files not placed in a .h .

SF.4: Include.hfiles before other declarations in a file

Reason

Minimize context dependencies and increase readability.

Example
#include 
<
vector
>

#include 
<
algorithm
>

#include 
<
string
>


// ... my code here ...
Example, bad
#include 
<
vector
>


// ... my code here ...

#include 
<
algorithm
>

#include 
<
string
>
Note

This applies to both.hand.cppfiles.

Note

There is an argument for insulating code from declarations and macros in header files by#includingheaders_after_the code we want to protect (as in the example labeled “bad”). However

  • that only works for one file (at one level): Use that technique in a header included with other headers and the vulnerability reappears.
  • a namespace (an “implementation namespace”) can protect against many context dependencies.
  • full protection and flexibility require modules . See also .
Enforcement

Easy.

SF.5: A.cppfile must include the.hfile(s) that defines its interface

Reason

This enables the compiler to do an early consistency check.

Example, bad
// foo.h:
void foo(int);
int bar(long);
int foobar(int);

// foo.cpp:
void foo(int) { /* ... */ }
int bar(double) { /* ... */ }
double foobar(int);

The errors will not be caught until link time for a program callingbarorfoobar.

Example
// foo.h:
void foo(int);
int bar(long);
int foobar(int);

// foo.cpp:
#include 
<
foo.h
>


void foo(int) { /* ... */ }
int bar(double) { /* ... */ }
double foobar(int);   // error: wrong return type

The return-type error forfoobaris now caught immediately whenfoo.cppis compiled. The argument-type error forbarcannot be caught until link time because of the possibility of overloading, but systematic use of.hfiles increases the likelihood that it is caught earlier by the programmer.

Enforcement

???

SF.6: Useusing namespacedirectives for transition, for foundation libraries (such asstd), or within a local scope (only)

Reason

using namespacecan lead to name clashes, so it should be used sparingly. However, it is not always possible to qualify every name from a namespace in user code (e.g., during transition) and sometimes a namespace is so fundamental and prevalent in a code base, that consistent qualification would be verbose and distracting.

Example
#include
<
string
>

#include
<
vector
>

#include
<
iostream
>

#include
<
memory
>

#include
<
algorithm
>


using namespace std;

// ...

Here (obviously), the standard library is used pervasively and apparantly no other library is used, so requiringstd::everywhere could be distracting.

Example

The use ofusing namespace std;leaves the programmer open to a name clash with a name from the standard library

#include
<
cmath
>

using namespace std;

int g(int x)
{
    int sqrt = 7;
    // ...
    return sqrt(x); // error
}

However, this is not particularly likely to lead to a resolution that is not an error and people who useusing namespace stdare supposed to know aboutstdand about this risk.

Note

A.cppfile is a form of local scope. There is little difference in the opportunities for name clashes in an N-line.cppcontaining ausing namespace X, an N-line function containing ausing namespace X, and M functions each containing ausing namespace Xwith N lines of code in total.

Note

Don’t writeusing namespacein a header file.

Enforcement

Flag multipleusing namespacedirectives for different namespaces in a single sourcefile.

SF.7: Don’t writeusing namespacein a header file

Reason

Doing so takes away an#includer’s ability to effectively disambiguate and to use alternatives.

Example
// bad.h
#include 
<
iostream
>

using namespace std; // bad

// user.cpp
#include "bad.h"

bool copy(/*... some parameters ...*/);    // some function that happens to be named copy

int main() {
    copy(/*...*/);    // now overloads local ::copy and std::copy, could be ambiguous
}
Enforcement

Flagusing namespaceat global scope in a header file.

SF.8: Use#includeguards for all.hfiles

Reason

To avoid files being#included several times.

Example
// file foobar.h:
#ifndef FOOBAR_H
#define FOOBAR_H
// ... declarations ...
#endif // FOOBAR_H
Enforcement

Flag.hfiles without#includeguards.

SF.9: Avoid cyclic dependencies among source files

Reason

Cycles complicates comprehension and slows down compilation. Complicates conversion to use language-supported modules (when they become available).

Note

Eliminate cycles; don’t just break them with#includeguards.

Example, bad
// file1.h:
#include "file2.h"

// file2.h:
#include "file3.h"

// file3.h:
#include "file1.h"
Enforcement

Flag all cycles.

SF.20: Usenamespaces to express logical structure

Reason

???

Example
???
Enforcement

???

SF.21: Don’t use an unnamed (anonymous) namespace in a header

Reason

It is almost always a bug to mention an unnamed namespace in a header file.

Example
???
Enforcement
  • Flag any use of an anonymous namespace in a header file.

SF.22: Use an unnamed (anonymous) namespace for all internal/nonexported entities

Reason

Nothing external can depend on an entity in a nested unnamed namespace. Consider putting every definition in an implementation source file in an unnamed namespace unless that is defining an “external/exported” entity.

Example

An API class and its members can’t live in an unnamed namespace; but any “helper” class or function that is defined in an implementation source file should be at an unnamed namespace scope.

???
Enforcement
  • ???

SL: The Standard Library

Using only the bare language, every task is tedious (in any language). Using a suitable library any task can be reasonably simple.

The standard library has steadily grown over the years. Its description in the standard is now larger than that of the language features. So, it is likely that this library section of the guidelines will eventually grow in size to equal or exceed all the rest.

« ??? We need another level of rule numbering ??? »

C++ Standard library component summary:

Standard-library rule summary:

SL.1: Use libraries wherever possible

Reason

Save time. Don’t re-invent the wheel. Don’t replicate the work of others. Benefit from other people’s work when they make improvements. Help other people when you make improvements.

SL.2: Prefer the standard library to other libraries

Reason

More people know the standard library. It is more likely to be stable, well-maintained, and widely available than your own code or most other libraries.

SL.con: Containers

???

Container rule summary:

SL.con.1: Prefer using STLarrayorvectorinstead of a C array

Reason

C arrays are less safe, and have no advantages overarrayandvector. For a fixed-length array, usestd::array, which does not degenerate to a pointer when passed to a function and does know its size. Also, like a built-in array, a stack-allocatedstd::arraykeeps its elements on the stack. For a variable-length array, usestd::vector, which additionally can change its size and handles memory allocation.

Example
int v[SIZE];                        // BAD

std::array
<
int, SIZE
>
 w;             // ok
Example
int* v = new int[initial_size];     // BAD, owning raw pointer
delete[] v;                         // BAD, manual delete

std::vector
<
int
>
 w(initial_size);   // ok
Enforcement
  • Flag declaration of a C array inside a function or class that also declares an STL container (to avoid excessive noisy warnings on legacy non-STL code). To fix: At least change the C array to a std::array .

SL.con.2: Prefer using STLvectorby default unless you have a reason to use a different container

Reason

vectorandarrayare the only standard containers that offer the fastest general-purpose access (random access, including being vectorization-friendly), the fastest default access pattern (begin-to-end or end-to-begin is prefetcher-friendly), and the lowest space overhead (contiguous layout has zero per-element overhead, which is cache-friendly). Usually you need to add and remove elements from the container, so usevectorby default; if you don’t need to modify the container’s size, usearray.

Even when other containers seem more suited, such amapfor O(log N) lookup performance or alistfor efficient insertion in the middle, avectorwill usually still perform better for containers up to a few KB in size.

Note

stringshould not be used as a container of individual characters. Astringis a textual string; if you want a container of characters, usevector</*char_type*/>orarray</*char_type*/>instead.

Exceptions

If you have a good reason to use another container, use that instead. For example:

  • Ifvectorsuits your needs but you don’t need the container to be variable size, usearrayinstead.

  • If you want a dictionary-style lookup container that guarantees O(K) or O(log N) lookups, the container will be larger (more than a few KB) and you perform frequent inserts so that the overhead of maintaining a sortedvectoris infeasible, go ahead and use anunordered_mapormapinstead.

Enforcement
  • Flag a vector whose size never changes after construction (such as because it’s const or because no non- const functions are called on it). To fix: Use an array instead.

SL.str: String

???

SL.io: Iostream

???

Iostream rule summary:

SL.io.1: Use character-level input only when you have to

???

SL.io.2: When reading, always consider ill-formed input

???

SL.io.50: Avoidendl

Reason

Theendlmanipulator is mostly equivalent to'\n'and"\n"; as most commonly used it simply slows down output by doing redundantflush()s. This slowdown can be significant compared toprintf-style output.

Example
cout 
<
<
 "Hello, World!" 
<
<
 endl;    // two output operations and a flush
cout 
<
<
 "Hello, World!\n";          // one output operation and no flush
Note

Forcin/cout(and equivalent) interaction, there is no reason to flush; that’s done automatically. For writing to a file, there is rarely a need toflush.

Note

Apart from the (occasionally important) issue of performance, the choice between'\n'andendlis almost completely aesthetic.

SL.regex: Regex

???

SL.chrono: Time

???

SL.C: The C standard library

???

C standard library rule summary:

A: Architectural Ideas

This section contains ideas about higher-level architectural ideas and libraries.

Architectural rule summary:

A.1 Separate stable from less stable part of code

???

A.2 Express potentially reusable parts as a library

Reason
Note

A library is a collection of declarations and definitions maintained, documented, and shipped together. A library could be a set of headers (a “header only library”) or a set of headers plus a set of object files. A library can be statically or dynamically linked into a program, or it may be#included

A.4 There should be no cycles among libraries

Reason
  • A cycle implies complication of the build process.
  • Cycles are hard to understand and may introduce indeterminism (unspecified behavior).
Note

A library can contain cyclic references in the definition of its components. For example:

???

However, a library should not depend on another that depends on it.

NR: Non-Rules and myths

This section contains rules and guidelines that are popular somewhere, but that we deliberately don’t recommend. We know full well that there have been times and places where these rules made sense, and we have used them ourselves at times. However, in the context of the styles of programming we recommend and support with the guidelines, these “non-rules” would do harm.

Even today, there can be contexts where the rules make sense. For example, lack of suitable tool support can make exceptions unsuitable in hard-real-time systems, but please don’t blindly trust “common wisdom” (e.g., unsupported statements about “efficiency”); such “wisdom” may be based on decades-old information or experienced from languages with very different properties than C++ (e.g., C or Java).

The positive arguments for alternatives to these non-rules are listed in the rules offered as “Alternatives”.

Non-rule summary:

NR.1: Don’t: All declarations should be at the top of a function

Reason (not to follow this rule)

This rule is a legacy of old programming languages that didn’t allow initialization of variables and constants after a statement. This leads to longer programs and more errors caused by uninitialized and wrongly initialized variables.

Example, bad
???

The larger the distance between the uninitialized variable and its use, the larger the chance of a bug. Fortunately, compilers catch many “used before set” errors.

Alternative

NR.2: Don’t: Have only a singlereturn-statement in a function

Reason (not to follow this rule)

The single-return rule can lead to unnecessarily convoluted code and the introduction of extra state variables. In particular, the single-return rule makes it harder to concentrate error checking at the top of a function.

Example
template
<
class T
>

//  requires Number
<
T
>

string sign(T x)
{
    if (x 
<
 0)
        return "negative";
    else if (x 
>
 0)
        return "positive";
    return "zero";
}

to use a single return only we would have to do something like

template
<
class T
>

//  requires Number
<
T
>

string sign(T x)        // bad
{
    string res;
    if (x 
<
 0)
        res = "negative";
    else if (x 
>
 0)
        res = "positive";
    else
        res = "zero";
    return res;
}

This is both longer and likely to be less efficient. The larger and more complicated the function is, the more painful the workarounds get. Of course many simple functions will naturally have just onereturnbecause of their simpler inherent logic.

Example
int index(const char* p)
{
    if (p == nullptr) return -1;  // error indicator: alternatively "throw nullptr_error{}"
    // ... do a lookup to find the index for p
    return i;
}

If we applied the rule, we’d get something like

int index2(const char* p)
{
    int i;
    if (p == nullptr)
        i = -1;  // error indicator
    else {
        // ... do a lookup to find the index for p
    }
    return i;
}

Note that we (deliberately) violated the rule against uninitialized variables because this style commonly leads to that. Also, this style is a temptation to use thegoto exitnon-rule.

Alternative
  • Keep functions short and simple
  • Feel free to use multiple return statements (and to throw exceptions).

NR.3: Don’t: Don’t use exceptions

Reason (not to follow this rule)

There seem to be three main reasons given for this non-rule:

  • exceptions are inefficient
  • exceptions lead to leaks and errors
  • exception performance is not predictable

There is no way we can settle this issue to the satisfaction of everybody. After all, the discussions about exceptions have been going on for 40+ years. Some languages cannot be used without exceptions, but others do not support them. This leads to strong traditions for the use and non-use of exceptions, and to heated debates.

However, we can briefly outline why we consider exceptions the best alternative for general-purpose programming and in the context of these guidelines. Simple arguments for and against are often inconclusive. There are specialized applications where exceptions indeed can be inappropriate (e.g., hard-real time systems without support for reliable estimates of the cost of handling an exception).

Consider the major objections to exceptions in turn

  • Exceptions are inefficient: Compared to what? When comparing make sure that the same set of errors are handled and that they are handled equivalently. In particular, do not compare a program that immediately terminate on seeing an error with a program that carefully cleans up resources before logging an error. Yes, some systems have poor exception handling implementations; sometimes, such implementations force us to use other error-handling approaches, but that’s not a fundamental problem with exceptions. When using an efficiency argument - in any context - be careful that you have good data that actually provides insight into the problem under discussion.
  • Exceptions lead to leaks and errors. They do not. If your program is a rat’s nest of pointers without an overall strategy for resource management, you have a problem whatever you do. If your system consists of a million lines of such code, you probably will not be able to use exceptions, but that’s a problem with excessive and undisciplined pointer use, rather than with exceptions. In our opinion, you need RAII to make exception-based error handling simple and safe – simpler and safer than alternatives.
  • Exception performance is not predictable If you are in a hard-real-time system where you must guarantee completion of a task in a given time, you need tools to back up such guarantees. As far as we know such tools are not available (at least not to most programmers).

Many, possibly most, problems with exceptions stem from historical needs to interact with messy old code.

The fundamental arguments for the use of exceptions are

  • They clearly separates error return from ordinary return
  • They cannot be forgotten or ignored
  • They can be used systematically

Remember

  • Exceptions are for reporting errors (in C++; other languages can have different uses for exceptions).
  • Exceptions are not for errors that can be handled locally.
  • Don’t try to catch every exception in every function (that’s tedious, clumsy, and leads to slow code).
  • Exceptions are not for errors that require instant termination of a module/system after a non-recoverable error.
Example
???
Alternative
  • RAII
  • Contracts/assertions: Use GSL’s Expects and Ensures (until we get language support for contracts)

NR.4: Don’t: Place each class declaration in its own source file

Reason (not to follow this rule)

The resulting number of files are hard to manage and can slow down compilation. Individual classes are rarely a good logical unit of maintenance and distribution.

Example
???
Alternative
  • Use namespaces containing logically cohesive sets of classes and functions.

NR.5: Don’t: Don’t do substantive work in a constructor; instead use two-phase initialization

Reason (not to follow this rule)

Following this rule leads to weaker invariants, more complicated code (having to deal with semi-constructed objects), and errors (when we didn’t deal correctly with semi-constructed objects consistently).

Example
???
Alternative
  • Always establish a class invariant in a constructor.
  • Don’t define an object before it is needed.

NR.6: Don’t: Place all cleanup actions at the end of a function andgoto exit

Reason (not to follow this rule)

gotois error-prone. This technique is a pre-exception technique for RAII-like resource and error handling.

Example, bad
void do_something(int n)
{
    if (n 
<
 100) goto exit;
    // ...
    int* p = (int*) malloc(n);
    // ...
    if (some_ error) goto_exit;
    // ...
exit:
    free(p);
}

and spot the bug.

Alternative
  • Use exceptions and RAII
  • for non-RAII resources, use finally .

NR.7: Don’t: Make all data membersprotected

Reason (not to follow this rule)

protecteddata is a source of errors.protecteddata can be manipulated from an unbounded amount of code in various places.protecteddata is the class hierarchy equivalent to global data.

Example
???
Alternative

RF: References

Many coding standards, rules, and guidelines have been written for C++, and especially for specialized uses of C++. Many

  • focus on lower-level issues, such as the spelling of identifiers
  • are written by C++ novices
  • see “stopping programmers from doing unusual things” as their primary aim
  • aim at portability across many compilers (some 10 years old)
  • are written to preserve decades old code bases
  • aim at a single application domain
  • are downright counterproductive
  • are ignored (must be ignored by programmers to get their work done well)

A bad coding standard is worse than no coding standard. However an appropriate set of guidelines are much better than no standards: “Form is liberating.”

Why can’t we just have a language that allows all we want and disallows all we don’t want (“a perfect language”)? Fundamentally, because affordable languages (and their tool chains) also serve people with needs that differ from yours and serve more needs than you have today. Also, your needs change over time and a general-purpose language is needed to allow you to adapt. A language that is ideal for today would be overly restrictive tomorrow.

Coding guidelines adapt the use of a language to specific needs. Thus, there cannot be a single coding style for everybody. We expect different organizations to provide additions, typically with more restrictions and firmer style rules.

Reference sections:

RF.rules: Coding rules

  • Boost Library Requirements and Guidelines . ???.
  • Bloomberg: BDE C++ Coding . Has a strong emphasis on code organization and layout.
  • Facebook: ???
  • GCC Coding Conventions . C++03 and (reasonably) a bit backwards looking.
  • Google C++ Style Guide . Geared toward C++03 and (also) older code bases. Google experts are now actively collaborating here on helping to improve these Guidelines, and hopefully to merge efforts so these can be a modern common set they could also recommend.
  • JSF++: JOINT STRIKE FIGHTER AIR VEHICLE C++ CODING STANDARDS . Document Number 2RDU00001 Rev C. December 2005. For flight control software. For hard real time. This means that it is necessarily very restrictive (“if the program fails somebody dies”). For example, no free store allocation or deallocation may occur after the plane takes off (no memory overflow and no fragmentation allowed). No exception may be used (because there was no available tool for guaranteeing that an exception would be handled within a fixed short time). Libraries used have to have been approved for mission critical applications. Any similarities to this set of guidelines are unsurprising because Bjarne Stroustrup was an author of JSF++. Recommended, but note its very specific focus.
  • Mozilla Portability Guide . As the name indicates, this aims for portability across many (old) compilers. As such, it is restrictive.
  • Geosoft.no: C++ Programming Style Guidelines . ???.
  • Possibility.com: C++ Coding Standard . ???.
  • SEI CERT: Secure C++ Coding Standard . A very nicely done set of rules (with examples and rationales) done for security-sensitive code. Many of their rules apply generally.
  • High Integrity C++ Coding Standard .
  • llvm . Somewhat brief, pre-C++11, and (not unreasonably) adjusted to its domain.
  • ???

RF.books: Books with coding guidelines

RF.C++: C++ Programming (C++11/C++14)

  • TC++PL4 : A thorough description of the C++ language and standard libraries for experienced programmers.
  • Tour++ : An overview of the C++ language and standard libraries for experienced programmers.
  • Programming: Principles and Practice using C++ : A textbook for beginners and relative novices.

RF.web: Websites

RS.video: Videos about “modern C++”

RF.man: Manuals

Acknowledgements

Thanks to the many people who contributed rules, suggestions, supporting information, references, etc.:

  • Peter Juhl
  • Neil MacIntosh
  • Axel Naumann
  • Andrew Pardoe
  • Gabriel Dos Reis
  • Zhuang, Jiangang (Jeff)
  • Sergey Zubkov

and see the contributor list on the github.

Copyright © trree.github.io 2017 all right reserved,powered by Gitbook该文件修订时间: 2017-04-13 12:57:30

results matching ""

    No results matching ""