Identifying all the components contributing to a well-designed program can be challenging. The following list serves as a starting point and is used to evaluate class projects. While there may be some exceptions, they are not typical and should not overshadow the general guidelines. Consider how these guidelines apply when reviewing a program rather than focusing on potential exceptions. The checklist will be extended as the semester progresses.
std::stringstd::string_viewOnly source files belong in the repository:
Generated files do not belong in the repository:
Generated files take up a lot of file space and prevent others from easily cloning and building the repository.
Do not use using namespace std;.
using namespace std; cin >> n;
Instead, use the prefix for everything in a namespace:
using namespace std; as they allow you to use the operator sv for std::string_view literals, e.g., "abc"sv
| Code | Explanation |
|---|---|
| Single space after start of comment | |
| Single space after keywords of statements | |
| Single space before operators | See above |
| Single space after operators | See above |
| No space between call name and argument list | |
| No space before statement-end semi-colon |
Use the following naming convention:
| Category | Semantics | Convention | Example |
|---|---|---|---|
| Classes | things not actions | PascalCase | class Token |
| Fields/Variables/Parameters | things | camelCase | int totalSize; |
| C++ Methods/Functions | actions | camelCase | void sortNames(); |
| C Functions | actions | under_score/camelCase | srcml_archive_get_unit() |
| Constant scalar values | things | UPPERCASE | const int MAX_SIZE = 100; |
| C-Preprocessor/cpp variables | things | UPPERCASE | #ifndef INCLUDE_XML_HPP |
std::vector, follow that naming convention.XMLBufferSingle-letter variable names are not to be used, except in certain, well-understood situations:
| Name | Purpose |
|---|---|
i, j, k |
loop-control variables |
c |
general character |
s |
general string |
n |
general int |
x |
general double |
Initialize temporary variables (those with local scope) when declared whenever possible.
| Initialization in Declaration (preferred) | Separate declaration |
|---|---|
Exception: Temporary variables initialized as an argument in a call:
int as the standard C++ integer type. Possible exceptions: size_t, std::size_typedouble as the standard C++ floating-point number type. There is hardly any reason to use float.Comment each section of code on the line before
const whenever possibleauto when initializing a declaration that includes a call, e.g., auto pc = buffer.begin()Use an explicit type instead of auto when initializing with a literal value to make certain you get the intended type
Prefer pre-increment over post-increment, i.e., prefer ++i; over i++;. Pre-increment is easier to understand, explain, and implement, e.g., post-increment is often implemented using pre-increment, especially when these operators are overloaded:
| Pre-increment Implementation | Post-increment Implementation |
|---|---|
This also applies to pre-decrement and post-decrement
Prefer pre-increment over post-increment in for loops. Using pre-increment in a for loop does not mean that the increment occurs before the loop.
| for-statement | Equivalent while-statement |
|---|---|
Favor the range-based for-statement instead of direct indexing (or iteration).
| Range-based for (preferred) | Direct Indexing |
|---|---|
@param For each parameter@return For the general return purpose@retval Specific return values, e.g., error or status codesThere is much more, so feel free to experiment. However, be consistent.
inline specifier. First, the compiler can ignore it. Second, the compiler automatically inlines small functions. Third, it is overused. The inline specifier has good purposes, but only in specific cases and not in this course. Note: Do not confuse the general programming term "inline a function" or "inline a method" with the inline specifier.Use the following tables for type T:
| Direction | Primitive | Object |
|---|---|---|
| IN | T | const T& |
| OUT, IN/OUT | T& | T& |
Examples:
std::string vs. const std::string& IN parameter, especially when passed repeatedly.const int, is not in the list. The reason is that the const is an unnecessary restriction for the function/method definition (implementation) and does not change the arguments allowed (caller).T)std::string&&, separatelyFor free functions:
| Type | Application | Example |
|---|---|---|
| T | Return copy of a local variable | |
| T | Return copy of a parameter | |
| const T& | Return a read-only reference to a parameter |
T&. Be very careful about this. It's safer to send a copy and may be just as efficient.std::string&&, separatelyThe following applies to all comments except file header comments.
Prefer the default and methods common to a container:
| Prefer | Explanation | |
|---|---|---|
| By default a std::string is an empty string | ||
| A std::string is a (specialized) container. Prefer container methods over comparison. One exception might be in a set of if statements (such as a nested if) that compares the string to other literal strings. | ||
| Prefer container method to assignment |
Prefer std::string_view if referring to an existing string or part of a string.
One of the problems with const std::string& parameter is that it makes a copy of a literal c-string.
| Declaration | Call | Memory |
|---|---|---|
void f(const std::string& s); |
std::string s; f(s); |
Refers to the existing string |
void f(const std::string& s); |
f("abc"); |
Creates a std::string copy |
The string type std::string_view was an addition to C++ in C++17. It allows you to efficiently refer to parts of a pre-existing std::string or a c-string char* without making a copy. It has many of the same methods as the std::string, and when the memory for the string already exists, it just refers to it, and no copies are made:
| Declaration | Call | Memory |
|---|---|---|
void f(std::string_view s); |
std::string s; f(s); |
Refers to the existing string |
void f(std::string_view s); |
f("abc"); |
Refers to the existing string |
The IN parameter of a std::string_view is passed by value instead of const reference as it consumes only a small amount of memory.
Write debugging/tracing messages to std::cerr, not std::cout:
std::cerr for error output |
|---|
std::cout for output, so this gives a way for the user to separate the intended output from error messagesstd::cerr is not buffered, while std::cout is. If your program crashes, any unbuffered output may be lost. So, you may not see your debugging messages.