Is `Ensures` the same as `assert`?
No. It is a placeholder for language support for contract postconditions.
Appendix A: Libraries
This section lists recommended libraries, and explicitly recommends a few.
??? Suitable for the general guide? I think not ???
Appendix B: Modernizing code
Ideally, we follow all rules in all code. Realistically, we have to deal with a lot of old code:
- application code written before the guidelines were formulated or known
- libraries written to older/different standards
- code written under "unusual" constraints
- code that we just haven't gotten around to modernizing
If we have a million lines of new code, the idea of "just changing it all at once" is typically unrealistic. Thus, we need a way of gradually modernizing a code base.
Upgrading older code to modern style can be a daunting task. Often, the old code is both a mess (hard to understand) and working correctly (for the current range of uses). Typically, the original programmer is not around and the test cases incomplete. The fact that the code is a mess dramatically increases the effort needed to make any change and the risk of introducing errors. Often, messy old code runs unnecessarily slowly because it requires outdated compilers and cannot take advantage of modern hardware. In many cases, automated "modernizer"-style tool support would be required for major upgrade efforts.
The purpose of modernizing code is to simplify adding new functionality, to ease maintenance, and to increase performance (throughput or latency), and to better utilize modern hardware. Making code "look pretty" or "follow modern style" are not by themselves reasons for change. There are risks implied by every change and costs (including the cost of lost opportunities) implied by having an outdated code base. The cost reductions must outweigh the risks.
But how?
There is no one approach to modernizing code. How best to do it depends on the code, the pressure for updates, the backgrounds of the developers, and the available tool. Here are some (very general) ideas:
In most circumstances, it is also impossible.
This would be a set of changes across the whole code base, but would most likely have huge benefits. Afterwards, code hidden behind those interfaces can be gradually modernized without affecting other code.
- The ideal is "just upgrade everything." That gives the most benefits for the shortest total time.
- We could convert a code base module for module, but any rules that affects interfaces (especially ABIs), such as use
span, cannot be done on a per-module basis. - We could convert code "bottom up" starting with the rules we estimate will give the greatest benefits and/or the least trouble in a given code base.
- We could start by focusing on the interfaces, e.g., make sure that no resources are lost and no pointer is misused.
Whichever way you choose, please note that the most advantages come with the highest conformance to the guidelines. The guidelines are not a random set of unrelated rules where you can randomly pick and choose with an expectation of success.
We would dearly love to hear about experience and about tools used. Modernization can be much faster, simpler, and safer when supported with analysis tools and even code transformation tools.
Appendix C: Discussion
This section contains follow-up material on rules and sets of rules. In particular, here we present further rationale, longer examples, and discussions of alternatives.
Discussion: Define and initialize data members in the order of member declaration
Data members are always initialized in the order they are declared in the class definition, so write them in that order in the constructor initialization list. Writing them in a different order just makes the code confusing because it won't run in the order you see, and that can make it hard to see order-dependent bugs.
class Employee {
string email, first, last;
public:
Employee(const char* firstName, const char* lastName);
// ...
};
Employee::Employee(const char* firstName, const char* lastName)
: first(firstName),
last(lastName),
// BAD: first and last not yet constructed
email(first + "." + last + "@acme.com")
{}
In this example, email will be constructed before first and last because it is declared first. That means its constructor will attempt to use first and last too soon -- not just before they are set to the desired values, but before they are constructed at all.
If the class definition and the constructor body are in separate files, the long-distance influence that the order of data member declarations has over the constructor's correctness will be even harder to spot.
References:
[\[Cline99\]](#Cline99) §22.03-11, [\[Dewhurst03\]](#Dewhurst03) §52-53, [\[Koenig97\]](#Koenig97) §4, [\[Lakos96\]](#Lakos96) §10.3.5, [\[Meyers97\]](#Meyers97) §13, [\[Murray93\]](#Murray93) §2.1.3, [\[Sutter00\]](#Sutter00) §47
Discussion: Use of =, {}, and () as initializers
???
Discussion: Use a factory function if you need "virtual behavior" during initialization
If your design wants virtual dispatch into a derived class from a base class constructor or destructor for functions like f and g, you need other techniques, such as a post-constructor -- a separate member function the caller must invoke to complete initialization, which can safely call f and g because in member functions virtual calls behave normally. Some techniques for this are shown in the References. Here's a non-exhaustive list of options:
- Pass the buck: Just document that user code must call the post-initialization function right after constructing an object.
- Post-initialize lazily: Do it during the first call of a member function. A Boolean flag in the base class tells whether or not post-construction has taken place yet.
- Use virtual base class semantics: Language rules dictate that the constructor of the most-derived class decides which base constructor will be invoked; you can use that to your advantage. (See [\[Taligent94\]](#Taligent94).)
- Use a factory function: This way, you can easily force a mandatory invocation of a post-constructor function.
Here is an example of the last option:
class B {
public:
B()
{
/* ... */
f(); // BAD: C.82: Don't call virtual functions in constructors and destructors
/* ... */
}
virtual void f() = 0;
};
class B {
protected:
class Token {};
public:
// constructor needs to be public so that make_shared can access it.
// protected access level is gained by requiring a Token.
explicit B(Token) { /* ... */ } // create an imperfectly initialized object
virtual void f() = 0;
template<class T>
static shared_ptr<T> create() // interface for creating shared objects
{
auto p = make_shared<T>(typename T::Token{});
p->post_initialize();
return p;
}
protected:
virtual void post_initialize() // called right after construction
{ /* ... */ f(); /* ... */ } // GOOD: virtual dispatch is safe
}
};
class D : public B { // some derived class
protected:
class Token {};
public:
// constructor needs to be public so that make_shared can access it.
// protected access level is gained by requiring a Token.
explicit D(Token) : B{ B::Token{} } {}
void f() override { /* ... */ };
protected:
template<class T>
friend shared_ptr<T> B::create();
};
shared_ptr<D> p = D::create<D>(); // creating a D object
This design requires the following discipline:
- Derived classes such as
Dmust not expose a publicly callable constructor. Otherwise,D's users could createDobjects that don't invokepost_initialize. - Allocation is limited to
operator new.Bcan, however, overridenew(see Items 45 and 46 in SuttAlex05). Dmust define a constructor with the same parameters thatBselected. Defining several overloads ofcreatecan assuage this problem, however; and the overloads can even be templated on the argument types.
If the requirements above are met, the design guarantees that post_initialize has been called for any fully constructed B-derived object. post_initialize doesn't need to be virtual; it can, however, invoke virtual functions freely.
In summary, no post-construction technique is perfect. The worst techniques dodge the whole issue by simply asking the caller to invoke the post-constructor manually. Even the best require a different syntax for constructing objects (easy to check at compile time) and/or cooperation from derived class authors (impossible to check at compile time).
References: [\[Alexandrescu01\]](#Alexandrescu01) §3, [\[Boost\]](#Boost), [\[Dewhurst03\]](#Dewhurst03) §75, [\[Meyers97\]](#Meyers97) §46, [\[Stroustrup00\]](#Stroustrup00) §15.4.3, [\[Taligent94\]](#Taligent94)
Discussion: Make base class destructors public and virtual, or protected and non-virtual
Should destruction behave virtually? That is, should destruction through a pointer to a base class be allowed? If yes, then base's destructor must be public in order to be callable, and virtual, otherwise calling it results in undefined behavior. Otherwise, it should be protected so that only derived classes can invoke it in their own destructors, and non-virtual since it doesn't need to behave virtually.
Example
The common case for a base class is that it's intended to have publicly derived classes, and so calling code is just about sure to use something like a shared_ptr<base>:
class Base {
public:
~Base(); // BAD, not virtual
virtual ~Base(); // GOOD
// ...
};
class Derived : public Base { /* ... */ };
{
unique_ptr<Base> pb = make_unique<Derived>();
// ...
} // ~pb invokes correct destructor only when ~Base is virtual
In rarer cases, such as policy classes, the class is used as a base class for convenience, not for polymorphic behavior. It is recommended to make those destructors protected and non-virtual:
class My_policy {
public:
virtual ~My_policy(); // BAD, public and virtual
protected:
~My_policy(); // GOOD
// ...
};
template<class Policy>
class customizable : Policy { /* ... */ }; // note: private inheritance
Note
This simple guideline illustrates a subtle issue and reflects modern uses of inheritance and object-oriented design principles.
For a base class Base, calling code might try to destroy derived objects through pointers to Base, such as when using a unique_ptr<Base>. If Base's destructor is public and non-virtual (the default), it can be accidentally called on a pointer that actually points to a derived object, in which case the behavior of the attempted deletion is undefined. This state of affairs has led older coding standards to impose a blanket requirement that all base class destructors must be virtual. This is overkill (even if it is the common case); instead, the rule should be to make base class destructors virtual if and only if they are public.
To write a base class is to define an abstraction (see Items 35 through 37). Recall that for each member function participating in that abstraction, you need to decide:
- Whether it should behave virtually or not.
- Whether it should be publicly available to all callers using a pointer to
Baseor else be a hidden internal implementation detail.
As described in Item 39, for a normal member function, the choice is between allowing it to be called via a pointer to Base non-virtually (but possibly with virtual behavior if it invokes virtual functions, such as in the NVI or Template Method patterns), virtually, or not at all. The NVI pattern is a technique to avoid public virtual functions.
Destruction can be viewed as just another operation, albeit with special semantics that make non-virtual calls dangerous or wrong. For a base class destructor, therefore, the choice is between allowing it to be called via a pointer to Base virtually or not at all; "non-virtually" is not an option. Hence, a base class destructor is virtual if it can be called (i.e., is public), and non-virtual otherwise.
Note that the NVI pattern cannot be applied to the destructor because constructors and destructors cannot make deep virtual calls. (See Items 39 and 55.)
Corollary: When writing a base class, always write a destructor explicitly, because the implicitly generated one is public and non-virtual. You can always =default the implementation if the default body is fine and you're just writing the function to give it the proper visibility and virtuality.
Exception
Some component architectures (e.g., COM and CORBA) don't use a standard deletion mechanism, and foster different protocols for object disposal. Follow the local patterns and idioms, and adapt this guideline as appropriate.
Consider also this rare case:
Bis both a base class and a concrete class that can be instantiated by itself, and so the destructor must be public forBobjects to be created and destroyed.- Yet
Balso has no virtual functions and is not meant to be used polymorphically, and so although the destructor is public it does not need to be virtual.
Then, even though the destructor has to be public, there can be great pressure to not make it virtual because as the first virtual function it would incur all the run-time type overhead when the added functionality should never be needed.
In this rare case, you could make the destructor public and non-virtual but clearly document that further-derived objects must not be used polymorphically as B's. This is what was done with std::unary_function.
In general, however, avoid concrete base classes (see Item 35). For example, unary_function is a bundle-of-typedefs that was never intended to be instantiated standalone. It really makes no sense to give it a public destructor; a better design would be to follow this Item's advice and give it a protected non-virtual destructor.
References: [\[SuttAlex05\]](#SuttAlex05) Item 50, [\[Cargill92\]](#Cargill92) pp. 77-79, 207, [\[Cline99\]](#Cline99) §21.06, 21.12-13, [\[Henricson97\]](#Henricson97) pp. 110-114, [\[Koenig97\]](#Koenig97) Chapters 4, 11, [\[Meyers97\]](#Meyers97) §14, [\[Stroustrup00\]](#Stroustrup00) §12.4.2, [\[Sutter02\]](#Sutter02) §27, [\[Sutter04\]](#Sutter04) §18
Discussion: Usage of noexcept
???
Discussion: Destructors, deallocation, and swap must never fail
Never allow an error to be reported from a destructor, a resource deallocation function (e.g., operator delete), or a swap function using throw. It is nearly impossible to write useful code if these operations can fail, and even if something does go wrong it nearly never makes any sense to retry. Specifically, types whose destructors might throw an exception are flatly forbidden from use with the C++ Standard Library. Most destructors are now implicitly noexcept by default.
Example
class Nefarious {
public:
Nefarious() { /* code that could throw */ } // ok
~Nefarious() { /* code that could throw */ } // BAD, should not throw
// ...
};
Nefariousobjects are hard to use safely even as local variables:
void test(string& s)
{
Nefarious n; // trouble brewing
string copy = s; // copy the string
} // destroy copy and then n
Here, copying `s` could throw, and if that throws and if `n`'s destructor then also throws, the program will exit via `std::terminate` because two exceptions can't be propagated simultaneously.
- Classes with
Nefariousmembers or bases are also hard to use safely, because their destructors must invokeNefarious' destructor, and are similarly poisoned by its bad behavior:
class Innocent_bystander {
Nefarious member; // oops, poisons the enclosing class's destructor
// ...
};
void test(string& s)
{
Innocent_bystander i; // more trouble brewing
string copy2 = s; // copy the string
} // destroy copy and then i
Here, if constructing `copy2` throws, we have the same problem because `i`'s destructor now also can throw, and if so we'll invoke `std::terminate`.
- You can't reliably create global or static
Nefariousobjects either:
static Nefarious n; // oops, any destructor exception can't be caught
- You can't reliably create arrays of
Nefarious:
void test()
{
std::array<Nefarious, 10> arr; // this line can std::terminate()
}
The behavior of arrays is undefined in the presence of destructors that throw because there is no reasonable rollback behavior that could ever be devised. Just think: What code can the compiler generate for constructing an `arr` where, if the fourth object's constructor throws, the code has to give up and in its cleanup mode tries to call the destructors of the already-constructed objects ... and one or more of those destructors throws? There is no satisfactory answer.
- You can't use
Nefariousobjects in standard containers:
std::vector<Nefarious> vec(10); // this line can std::terminate()
The standard library forbids all destructors used with it from throwing. You can't store `Nefarious` objects in standard containers or use them with any other part of the standard library.
Note
These are key functions that must not fail because they are necessary for the two key operations in transactional programming: to back out work if problems are encountered during processing, and to commit work if no problems occur. If there's no way to safely back out using no-fail operations, then no-fail rollback is impossible to implement. If there's no way to safely commit state changes using a no-fail operation (notably, but not limited to, swap), then no-fail commit is impossible to implement.
Consider the following advice and requirements found in the C++ Standard:
If a destructor called during stack unwinding exits with an exception, terminate is called (15.5.1). So destructors should generally catch exceptions and not let them propagate out of the destructor. --[\[C++03\]](#Cplusplus03) §15.2(3) No destructor operation defined in the C++ Standard Library (including the destructor of any type that is used to instantiate a standard-library template) will throw an exception. --[\[C++03\]](#Cplusplus03) §17.4.4.8(3)
Deallocation functions, including specifically overloaded operator delete and operator delete[], fall into the same category, because they too are used during cleanup in general, and during exception handling in particular, to back out of partial work that needs to be undone. Besides destructors and deallocation functions, common error-safety techniques rely also on swap operations never failing -- in this case, not because they are used to implement a guaranteed rollback, but because they are used to implement a guaranteed commit. For example, here is an idiomatic implementation of operator= for a type T that performs copy construction followed by a call to a no-fail swap:
T& T::operator=(const T& other)
{
auto temp = other;
swap(temp);
return *this;
}
(See also Item 56. ???)
Fortunately, when releasing a resource, the scope for failure is definitely smaller. If using exceptions as the error reporting mechanism, make sure such functions handle all exceptions and other errors that their internal processing might generate. (For exceptions, simply wrap everything sensitive that your destructor does in a try/catch(...) block.) This is particularly important because a destructor might be called in a crisis situation, such as failure to allocate a system resource (e.g., memory, files, locks, ports, windows, or other system objects).
When using exceptions as your error handling mechanism, always document this behavior by declaring these functions noexcept. (See Item 75.)
References: [\[SuttAlex05\]](#SuttAlex05) Item 51; [\[C++03\]](#Cplusplus03) §15.2(3), §17.4.4.8(3), [\[Meyers96\]](#Meyers96) §11, [\[Stroustrup00\]](#Stroustrup00) §14.4.7, §E.2-4, [\[Sutter00\]](#Sutter00) §8, §16, [\[Sutter02\]](#Sutter02) §18-19
Define Copy, move, and destroy consistently
Reason
???
Note
If you define a copy constructor, you must also define a copy assignment operator.
Note
If you define a move constructor, you must also define a move assignment operator.
Example
class X {
public:
X(const X&) { /* stuff */ }
// BAD: failed to also define a copy assignment operator
X(x&&) noexcept { /* stuff */ }
// BAD: failed to also define a move assignment operator
// ...
};
X x1;
X x2 = x1; // ok
x2 = x1; // pitfall: either fails to compile, or does something suspicious
If you define a destructor, you should not use the compiler-generated copy or move operation; you probably need to define or suppress copy and/or move.
class X {
HANDLE hnd;
// ...
public:
~X() { /* custom stuff, such as closing hnd */ }
// suspicious: no mention of copying or moving -- what happens to hnd?
};
X x1;
X x2 = x1; // pitfall: either fails to compile, or does something suspicious
x2 = x1; // pitfall: either fails to compile, or does something suspicious
If you define copying, and any base or member has a type that defines a move operation, you should also define a move operation.
class X {
string s; // defines more efficient move operations
// ... other data members ...
public:
X(const X&) { /* stuff */ }
X& operator=(const X&) { /* stuff */ }
// BAD: failed to also define a move construction and move assignment
// (why wasn't the custom "stuff" repeated here?)
};
X test()
{
X local;
// ...
return local; // pitfall: will be inefficient and/or do the wrong thing
}
If you define any of the copy constructor, copy assignment operator, or destructor, you probably should define the others.
Note
If you need to define any of these five functions, it means you need it to do more than its default behavior -- and the five are asymmetrically interrelated. Here's how:
- If you write/disable either of the copy constructor or the copy assignment operator, you probably need to do the same for the other: If one does "special" work, probably so should the other because the two functions should have similar effects. (See Item 53, which expands on this point in isolation.)
- If you explicitly write the copying functions, you probably need to write the destructor: If the "special" work in the copy constructor is to allocate or duplicate some resource (e.g., memory, file, socket), you need to deallocate it in the destructor.
- If you explicitly write the destructor, you probably need to explicitly write or disable copying: If you have to write a non-trivial destructor, it's often because you need to manually release a resource that the object held. If so, it is likely that those resources require careful duplication, and then you need to pay attention to the way objects are copied and assigned, or disable copying completely.
In many cases, holding properly encapsulated resources using RAII "owning" objects can eliminate the need to write these operations yourself. (See Item 13.)
Prefer compiler-generated (including =default) special members; only these can be classified as "trivial", and at least one major standard library vendor heavily optimizes for classes having trivial special members. This is likely to become common practice.
Exceptions: When any of the special functions are declared only to make them non-public or virtual, but without special semantics, it doesn't imply that the others are needed. In rare cases, classes that have members of strange types (such as reference members) are an exception because they have peculiar copy semantics. In a class holding a reference, you likely need to write the copy constructor and the assignment operator, but the default destructor already does the right thing. (Note that using a reference member is almost always wrong.)
References: [\[SuttAlex05\]](#SuttAlex05) Item 52; [\[Cline99\]](#Cline99) §30.01-14, [\[Koenig97\]](#Koenig97) §4, [\[Stroustrup00\]](#Stroustrup00) §5.5, §10.4, [\[SuttHysl04b\]](#SuttHysl04b)
Resource management rule summary:
- Provide strong resource safety; that is, never leak anything that you think of as a resource
- Never return or throw while holding a resource not owned by a handle
- A "raw" pointer or reference is never a resource handle
- Never let a pointer outlive the object it points to
- Use templates to express containers (and other resource handles)
- Return containers by value (relying on move or copy elision for efficiency)
- If a class is a resource handle, it needs a constructor, a destructor, and copy and/or move operations
- If a class is a container, give it an initializer-list constructor
Discussion: Provide strong resource safety; that is, never leak anything that you think of as a resource
Reason
Prevent leaks. Leaks can lead to performance degradation, mysterious error, system crashes, and security violations.
Alternative formulation: Have every resource represented as an object of some class managing its lifetime.
Example
template<class T>
class Vector {
private:
T* elem; // sz elements on the free store, owned by the class object
int sz;
// ...
};
This class is a resource handle. It manages the lifetime of the Ts. To do so, Vector must define or delete the copy, move, and destruction operations.
Example
??? "odd" non-memory resource ???
Enforcement
The basic technique for preventing leaks is to have every resource owned by a resource handle with a suitable destructor. A checker can find "naked news". Given a list of C-style allocation functions (e.g., fopen()), a checker can also find uses that are not managed by a resource handle. In general, "naked pointers" can be viewed with suspicion, flagged, and/or analyzed. A complete list of resources cannot be generated without human input (the definition of "a resource" is necessarily too general), but a tool can be "parameterized" with a resource list.
Discussion: Never return or throw while holding a resource not owned by a handle
Reason
That would be a leak.
Example
void f(int i)
{
FILE* f = fopen("a file", "r");
ifstream is { "another file" };
// ...
if (i == 0) return;
// ...
fclose(f);
}
If i == 0 the file handle for a file is leaked. On the other hand, the ifstream for another file will correctly close its file (upon destruction). If you must use an explicit pointer, rather than a resource handle with specific semantics, use a unique_ptr or a shared_ptr with a custom deleter:
void f(int i)
{
unique_ptr<FILE, int(*)(FILE*)> f(fopen("a file", "r"), fclose);
// ...
if (i == 0) return;
// ...
}
Better:
void f(int i)
{
ifstream input {"a file"};
// ...
if (i == 0) return;
// ...
}
Enforcement
A checker must consider all "naked pointers" suspicious. A checker probably must rely on a human-provided list of resources. For starters, we know about the standard-library containers, string, and smart pointers. The use of span and string_view should help a lot (they are not resource handles).
Discussion: A "raw" pointer or reference is never a resource handle
Reason
To be able to distinguish owners from views.
Note
This is independent of how you "spell" pointer: T*, T&, Ptr<T> and Range<T> are not owners.
Discussion: Never let a pointer outlive the object it points to
Reason
To avoid extremely hard-to-find errors. Dereferencing such a pointer is undefined behavior and could lead to violations of the type system.
Example
string* bad() // really bad
{
vector<string> v = { "This", "will", "cause", "trouble", "!" };
// leaking a pointer into a destroyed member of a destroyed object (v)
return &v[0];
}
void use()
{
string* p = bad();
vector<int> xx = {7, 8, 9};
// undefined behavior: x might not be the string "This"
string x = *p;
// undefined behavior: we don't know what (if anything) is allocated a location p
*p = "Evil!";
}
The strings of v are destroyed upon exit from bad() and so is v itself. The returned pointer points to unallocated memory on the free store. This memory (pointed into by p) might have been reallocated by the time *p is executed. There might be no string to read and a write through p could easily corrupt objects of unrelated types.
Enforcement
Most compilers already warn about simple cases and have the information to do more. Consider any pointer returned from a function suspect. Use containers, resource handles, and views (e.g., span known not to be resource handles) to lower the number of cases to be examined. For starters, consider every class with a destructor as resource handle.
Discussion: Use templates to express containers (and other resource handles)
Reason
To provide statically type-safe manipulation of elements.
Example
template<typename T> class Vector {
// ...
T* elem; // point to sz elements of type T
int sz;
};
Discussion: Return containers by value (relying on move or copy elision for efficiency)
Reason
To simplify code and eliminate a need for explicit memory management. To bring an object into a surrounding scope, thereby extending its lifetime.
See also: F.20, the general item about "out" output values
Example
vector<int> get_large_vector()
{
return ...;
}
auto v = get_large_vector(); // return by value is ok, most modern compilers will do copy elision
Exception
See the Exceptions in F.20.
Enforcement
Check for pointers and references returned from functions and see if they are assigned to resource handles (e.g., to a unique_ptr).
Discussion: If a class is a resource handle, it needs a constructor, a destructor, and copy and/or move operations
Reason
To provide complete control of the lifetime of the resource. To provide a coherent set of operations on the resource.
Example
??? Messing with pointers
Note
If all members are resource handles, rely on the compiler-generated operations where possible.
template<typename T> struct Named {
string name;
T value;
};
Now Named has a default constructor, a destructor, and efficient copy and move operations, provided T has.
Enforcement
In general, a tool cannot know if a class is a resource handle. However, if a class has some of the default operations, it should have all, and if a class has a member that is a resource handle, it should be considered as resource handle.
Discussion: If a class is a container, give it an initializer-list constructor
Reason
It is common to need an initial set of elements.
Example
template<typename T> class Vector {
public:
Vector(std::initializer_list<T>);
// ...
};
Vector<string> vs { "Nygaard", "Ritchie" };
Enforcement
When is a class a container? ???
Appendix D: Supporting tools
This section contains a list of tools that directly support adoption of the C++ Core Guidelines. This list is not intended to be an exhaustive list of tools that are helpful in writing good C++ code. If a tool is designed specifically to support and links to the C++ Core Guidelines it is a candidate for inclusion.
Tools: Clang-tidy
Clang-tidy has a set of rules that specifically enforce the C++ Core Guidelines. These rules are named in the pattern cppcoreguidelines-*.
Tools: CppCoreCheck
The Microsoft compiler's C++ code analysis contains a set of rules specifically aimed at enforcement of the C++ Core Guidelines.
Glossary
A relatively informal definition of terms used in the guidelines (based off the glossary in Programming: Principles and Practice using C++)
More information on many topics about C++ can be found on the Standard C++ Foundation's site.
A class is made abstract by having a pure virtual function or only protected constructors.
Often an approximation is a result of trade-offs among ideals.
Sometimes complexity is used to (simply) mean an estimate of the number of operations needed to execute an algorithm.
Typically a constructor establishes an invariant and often acquires resources needed for an object to be used (which are then typically released by a destructor).
Unfortunately, a specification can be incomplete or inconsistent, or can fail to meet users' reasonable expectations. Thus, to produce acceptable code, we sometimes have to do more than just follow the formal specification.
Ideally, cost should be a function of complexity.
Simplified definition: a declaration that allocates memory.
A generic algorithm will work for all argument types that meet its requirements. In C++, generic programming typically uses templates.
For example, a name from a nested (inner) scope can prevent that same name from an outer (enclosing) scope from being directly used.
In reality, such recursion is never infinite but is terminated by some hardware error.
In particular, an object of a regular type can be copied and the result of a copy is a separate object that compares equal to the original. See also semiregular type.
- ABI: Application Binary Interface, a specification for a specific hardware platform combined with the operating system. Contrast with API.
- abstract class: a class that cannot be directly used to create objects; often used to define an interface to derived classes.
- abstraction: a description of something that selectively and deliberately ignores (hides) details (e.g., implementation details); selective ignorance.
- address: a value that allows us to find an object in a computer's memory.
- algorithm: a procedure or formula for solving a problem; a finite series of computational steps to produce a result.
- alias: an alternative way of referring to an object; often a name, pointer, or reference.
- API: Application Programming Interface, a set of functions that form the communication between various software components. Contrast with ABI.
- application: a program or a collection of programs that is considered an entity by its users.
- approximation: something (e.g., a value or a design) that is close to the perfect or ideal (value or design).
- argument: a value passed to a function or a template, in which it is accessed through a parameter.
- array: a homogeneous sequence of elements, usually numbered, e.g.,
[0:max). - assertion: a statement inserted into a program to state (assert) that something must always be true at this point in the program.
- base class: a type that is intended to be derived from (e.g., has a non-
finalvirtual function), and objects of the type are intended to be used only indirectly (e.g., by pointer). \[In strict terms, "base class" could be defined as "something we derived from" but we are specifying in terms of the class designer's intent.\] Typically a base class has one or more virtual functions. - bit: the basic unit of information in a computer. A bit can have the value 0 or the value 1.
- bug: an error in a program.
- byte: the basic unit of addressing in most computers. Typically, a byte holds 8 bits.
- class: a user-defined type that can contain data members, function members, and member types.
- code: a program or a part of a program; ambiguously used for both source code and object code.
- compiler: a program that turns source code into object code.
- complexity: a hard-to-precisely-define notion or measure of the difficulty of constructing a solution to a problem or of the solution itself.
- computation: the execution of some code, usually taking some input and producing some output.
- concept: (1) a notion, and idea; (2) a set of requirements, usually for a template argument.
- concrete type: a type that is not a base class, and objects of the type are intended to be used directly (not only by pointer/indirection), its size is known, it can typically be allocated anywhere the programmer wants (e.g., stack or statically).
- constant: a value that cannot be changed (in a given scope); not mutable.
- constructor: an operation that initializes ("constructs") an object.
- container: an object that holds elements (other objects).
- copy: an operation that makes two objects have values that compare equal. See also move.
- correctness: a program or a piece of a program is correct if it meets its specification.
- cost: the expense (e.g., in programmer time, run time, or space) of producing a program or of executing it.
- customization point: ???
- data: values used in a computation.
- debugging: the act of searching for and removing errors from a program; usually far less systematic than testing.
- declaration: the specification of a name with its type in a program.
- definition: a declaration of an entity that supplies all information necessary to complete a program using the entity.
- derived class: a class derived from one or more base classes.
- design: an overall description of how a piece of software should operate to meet its specification.
- destructor: an operation that is implicitly invoked (called) when an object is destroyed (e.g., at the end of a scope). Often, it releases resources.
- encapsulation: protecting something meant to be private (e.g., implementation details) from unauthorized access.
- error: a mismatch between reasonable expectations of program behavior (often expressed as a requirement or a users' guide) and what a program actually does.
- executable: a program ready to be run (executed) on a computer.
- feature creep: a tendency to add excess functionality to a program "just in case."
- file: a container of permanent information in a computer.
- floating-point number: a computer's approximation of a real number, such as 7.93 and 10.78e-3.
- function: a named unit of code that can be invoked (called) from different parts of a program; a logical unit of computation.
- generic programming: a style of programming focused on the design and efficient implementation of algorithms.
- global variable: technically, a named object in namespace scope.
- handle: a class that allows access to another through a member pointer or reference. See also resource, copy, move.
- header: a file containing declarations used to share interfaces between parts of a program.
- hiding: the act of preventing a piece of information from being directly seen or accessed.
- ideal: the perfect version of something we are striving for. Usually we have to make trade-offs and settle for an approximation.
- implementation: (1) the act of writing and testing code; (2) the code that implements a program.
- infinite loop: a loop where the termination condition never becomes true. See iteration.
- infinite recursion: a recursion that doesn't end until the machine runs out of memory to hold the calls.
- information hiding: the act of separating interface and implementation, thus hiding implementation details not meant for the user's attention and providing an abstraction.
- initialize: giving an object its first (initial) value.
- input: values used by a computation (e.g., function arguments and characters typed on a keyboard).
- integer: a whole number, such as 42 and -99.
- interface: a declaration or a set of declarations specifying how a piece of code (such as a function or a class) can be called.
- invariant: something that must be always true at a given point (or points) of a program; typically used to describe the state (set of values) of an object or the state of a loop before entry into the repeated statement.
- iteration: the act of repeatedly executing a piece of code; see recursion.
- iterator: an object that identifies an element of a sequence.
- ISO: International Organization for Standardization. The C++ language is an ISO standard, ISO/IEC 14882. More information at iso.org.
- library: a collection of types, functions, classes, etc. implementing a set of facilities (abstractions) meant to be potentially used as part of more than one program.
- lifetime: the time from the initialization of an object until it becomes unusable (goes out of scope, is deleted, or the program terminates).
- linker: a program that combines object code files and libraries into an executable program.
- literal: a notation that directly specifies a value, such as 12 specifying the integer value "twelve."
- loop: a piece of code executed repeatedly; in C++, typically a for-statement or a
while-statement. - move: an operation that transfers a value from one object to another leaving behind a value representing "empty." See also copy.
- move-only type: a concrete type that is movable but not copyable.
- mutable: changeable; the opposite of immutable, constant, and invariable.
- object: (1) an initialized region of memory of a known type which holds a value of that type; (2) a region of memory.
- object code: output from a compiler intended as input for a linker (for the linker to produce executable code).
- object file: a file containing object code.
- object-oriented programming: (OOP) a style of programming focused on the design and use of classes and class hierarchies.
- operation: something that can perform some action, such as a function and an operator.
- output: values produced by a computation (e.g., a function result or lines of characters written on a screen).
- overflow: producing a value that cannot be stored in its intended target.
- overload: defining two functions or operators with the same name but different argument (operand) types.
- override: defining a function in a derived class with the same name and argument types as a virtual function in the base class, thus making the function callable through the interface defined by the base class.
- owner: an object responsible for releasing a resource.
- paradigm: a somewhat pretentious term for design or programming style; often used with the (erroneous) implication that there exists a paradigm that is superior to all others.
- parameter: a declaration of an explicit input to a function or a template. When called, a function can access the arguments passed through the names of its parameters.
- pointer: (1) a value used to identify a typed object in memory; (2) a variable holding such a value.
- post-condition: a condition that must hold upon exit from a piece of code, such as a function or a loop.
- pre-condition: a condition that must hold upon entry into a piece of code, such as a function or a loop.
- program: code (possibly with associated data) that is sufficiently complete to be executed by a computer.
- programming: the art of expressing solutions to problems as code.
- programming language: a language for expressing programs.
- pseudo code: a description of a computation written in an informal notation rather than a programming language.
- pure virtual function: a virtual function that must be overridden in a derived class.
- RAII: ("Resource Acquisition Is Initialization") a basic technique for resource management based on scopes.
- range: a sequence of values that can be described by a start point and an end point. For example,
[0:5)means the values 0, 1, 2, 3, and 4. - recursion: the act of a function calling itself; see also iteration.
- reference: (1) a value describing the location of a typed value in memory; (2) a variable holding such a value.
- regular expression: a notation for patterns in character strings.
- regular: a semiregular type that is equality-comparable (see
std::regularconcept). After a copy, the copied object compares equal to the original object. A regular type behaves similarly to built-in types likeintand can be compared with==. - requirement: (1) a description of the desired behavior of a program or part of a program; (2) a description of the assumptions a function or template makes of its arguments.
- resource: something that is acquired and must later be released, such as a file handle, a lock, or memory. See also handle, owner.
- rounding: conversion of a value to the mathematically nearest value of a less precise type.
- RTTI: Run-Time Type Information. ???
- scope: the region of program text (source code) in which a name can be referred to.
- semiregular: a concrete type that is copyable (including movable) and default-constructible (see
std::semiregularconcept). The result of a copy is an independent object with the same value as the original. A semiregular type behaves roughly like a built-in type likeint, but possibly without a==operator. See also regular type. - sequence: elements that can be visited in a linear order.
- software: a collection of pieces of code and associated data; often used interchangeably with program.
- source code: code as produced by a programmer and (in principle) readable by other programmers.
- source file: a file containing source code.
- specification: a description of what a piece of code should do.
- standard: an officially agreed upon definition of something, such as a programming language.
- state: a set of values.
- STL: the containers, iterators, and algorithms part of the standard library.
- string: a sequence of characters.
- style: a set of techniques for programming leading to a consistent use of language features; sometimes used in a very restricted sense to refer just to low-level rules for naming and appearance of code.
- subtype: derived type; a type that has all the properties of a type and possibly more.
- supertype: base type; a type that has a subset of the properties of a type.
- system: (1) a program or a set of programs for performing a task on a computer; (2) a shorthand for "operating system", that is, the fundamental execution environment and tools for a computer.
- TS: Technical Specification, A Technical Specification addresses work still under technical development, or where it is believed that there will be a future, but not immediate, possibility of agreement on an International Standard. A Technical Specification is published for immediate use, but it also provides a means to obtain feedback. The aim is that it will eventually be transformed and republished as an International Standard.
- template: a class or a function parameterized by one or more types or (compile-time) values; the basic C++ language construct supporting generic programming.
- testing: a systematic search for errors in a program.
- trade-off: the result of balancing several design and implementation criteria.
- truncation: loss of information in a conversion from a type into another that cannot exactly represent the value to be converted.
- type: something that defines a set of possible values and a set of operations for an object.
- uninitialized: the (undefined) state of an object before it is initialized.
- unit: (1) a standard measure that gives meaning to a value (e.g., km for a distance); (2) a distinguished (e.g., named) part of a larger whole.
- use case: a specific (typically simple) use of a program meant to test its functionality and demonstrate its purpose.
- value: a set of bits in memory interpreted according to a type.
- value type: a term some people use to mean a regular or semiregular type.
- variable: a named object of a given type; contains a value unless uninitialized.
- virtual function: a member function that can be overridden in a derived class.
- word: a basic unit of memory in a computer, often the unit used to hold an integer.
To-do: Unclassified proto-rules
This is our to-do list. Eventually, the entries will become rules or parts of rules. Alternatively, we will decide that no change is needed and delete the entry.
- No long-distance friendship
- Should physical design (what's in a file) and large-scale design (libraries, groups of libraries) be addressed?
- Namespaces
- Avoid using directives in the global scope (except for std, and other "fundamental" namespaces (e.g. experimental))
- How granular should namespaces be? All classes/functions designed to work together and released together (as defined in Sutter/Alexandrescu) or something narrower or wider?
- Should there be inline namespaces (à la
std::literals::*_literals)? - Avoid implicit conversions
- Const member functions should be thread safe ... aka, but I don't really change the variable, just assign it a value the first time it's called ... argh
- Always initialize variables, use initialization lists for data members.
- Anyone writing a public interface which takes or returns
void*should have their toes set on fire. That one has been a personal favorite of mine for a number of years. :) - Use
const-ness wherever possible: member functions, variables and (yippee)const_iterators - Use
auto (size)vs.{initializers}vs.{Extent{size}}- Don't overabstract
- Never pass a pointer down the call stack
- falling through a function bottom
- Should there be guidelines to choose between polymorphisms? YES. classic (virtual functions, reference semantics) vs. Sean Parent style (value semantics, type-erased, kind of like
std::function) vs. CRTP/static? YES Perhaps even vs. tag dispatch? - should virtual calls be banned from ctors/dtors in your guidelines? YES. A lot of people ban them, even though I think it's a big strength of C++ that they are ??? -preserving (D disappointed me so much when it went the Java way). WHAT WOULD BE A GOOD EXAMPLE?
- Speaking of lambdas, what would weigh in on the decision between lambdas and (local?) classes in algorithm calls and other callback scenarios?
- And speaking of
std::bind, Stephen T. Lavavej criticizes it so much I'm starting to wonder if it is indeed going to fade away in future. Should lambdas be recommended instead? - What to do with leaks out of temporaries? :
p = (s1 + s2).c_str(); - pointer/iterator invalidation leading to dangling pointers:
void bad()
{
int* p = new int[700];
int* q = &p[7];
delete p;
vector<int> v(700);
int* q2 = &v[7];
v.resize(900);
// ... use q and q2 ...
}
- LSP
- private inheritance vs/and membership
- avoid static class members variables (race conditions, almost-global variables)
- Use RAII lock guards (
lock_guard,unique_lock,shared_lock), never callmutex.lockandmutex.unlockdirectly (RAII) - Prefer non-recursive locks (often used to work around bad reasoning, overhead)
- Join your threads! (because of
std::terminatein destructor if not joined or detached ... is there a good reason to detach threads?) -- ??? could support library provide a RAII wrapper forstd::thread? - If two or more mutexes must be acquired at the same time, use
std::lock(or another deadlock avoidance algorithm?) - When using a
condition_variable, always protect the condition by a mutex (atomic bool whose value is set outside of the mutex is wrong!), and use the same mutex for the condition variable itself. - Never use
atomic_compare_exchange_strongwithstd::atomic<user-defined-struct>(differences in padding matter, whilecompare_exchange_weakin a loop converges to stable padding) - individual
shared_futureobjects are not thread-safe: two threads cannot wait on the sameshared_futureobject (they can wait on copies of ashared_futurethat refer to the same shared state) - individual
shared_ptrobjects are not thread-safe: different threads can call non-constmember functions on differentshared_ptrs that refer to the same shared object, but one thread cannot call a non-constmember function of ashared_ptrobject while another thread accesses that sameshared_ptrobject (if you need that, consideratomic_shared_ptrinstead)
- rules for arithmetic
Bibliography
\[Abrahams01]: D. Abrahams. Exception-Safety in Generic Components.
\[Alexandrescu01]: A. Alexandrescu. Modern C++ Design (Addison-Wesley, 2001).
\[C++03]: ISO/IEC 14882:2003(E), Programming Languages — C++ (updated ISO and ANSI C++ Standard including the contents of (C++98) plus errata corrections).
\[Cargill92]: T. Cargill. C++ Programming Style (Addison-Wesley, 1992).
\[Cline99]: M. Cline, G. Lomow, and M. Girou. C++ FAQs (2ndEdition) (Addison-Wesley, 1999).
\[Dewhurst03]: S. Dewhurst. C++ Gotchas (Addison-Wesley, 2003).
\[Henricson97]: M. Henricson and E. Nyquist. Industrial Strength C++ (Prentice Hall, 1997).
\[Koenig97]: A. Koenig and B. Moo. Ruminations on C++ (Addison-Wesley, 1997).
\[Lakos96]: J. Lakos. Large-Scale C++ Software Design (Addison-Wesley, 1996).
\[Meyers96]: S. Meyers. More Effective C++ (Addison-Wesley, 1996).
\[Meyers97]: S. Meyers. Effective C++ (2nd Edition) (Addison-Wesley, 1997).
\[Meyers01]: S. Meyers. Effective STL (Addison-Wesley, 2001).
\[Meyers05]: S. Meyers. Effective C++ (3rd Edition) (Addison-Wesley, 2005).
\[Meyers15]: S. Meyers. Effective Modern C++ (O'Reilly, 2015).
\[Murray93]: R. Murray. C++ Strategies and Tactics (Addison-Wesley, 1993).
\[Stroustrup94]: B. Stroustrup. The Design and Evolution of C++ (Addison-Wesley, 1994).
\[Stroustrup00]: B. Stroustrup. The C++ Programming Language (Special 3rdEdition) (Addison-Wesley, 2000).
\[Stroustrup05]: B. Stroustrup. A rationale for semantically enhanced library languages.
\[Stroustrup13]: B. Stroustrup. The C++ Programming Language (4th Edition). Addison-Wesley 2013.
\[Stroustrup14]: B. Stroustrup. A Tour of C++. Addison-Wesley 2014.
\[Stroustrup15]: B. Stroustrup, Herb Sutter, and G. Dos Reis: A brief introduction to C++'s model for type- and resource-safety.
\[SuttHysl04b]: H. Sutter and J. Hyslop. Collecting Shared Objects (C/C++ Users Journal, 22(8), August 2004).
\[SuttAlex05]: H. Sutter and A. Alexandrescu. C++ Coding Standards. Addison-Wesley 2005.
\[Sutter00]: H. Sutter. Exceptional C++ (Addison-Wesley, 2000).
\[Sutter02]: H. Sutter. More Exceptional C++ (Addison-Wesley, 2002).
\[Sutter04]: H. Sutter. Exceptional C++ Style (Addison-Wesley, 2004).
\[Taligent94]: Taligent's Guide to Designing Programs (Addison-Wesley, 1994).
- <a name="Abrahams01"></a>
- <a name="Alexandrescu01"></a>
- <a name="Cplusplus03"></a>
- <a name="Cargill92"></a>
- <a name="Cline99"></a>
- <a name="Dewhurst03"></a>
- <a name="Henricson97"></a>
- <a name="Koenig97"></a>
- <a name="Lakos96"></a>
- <a name="Meyers96"></a>
- <a name="Meyers97"></a>
- <a name="Meyers01"></a>
- <a name="Meyers05"></a>
- <a name="Meyers15"></a>
- <a name="Murray93"></a>
- <a name="Stroustrup94"></a>
- <a name="Stroustrup00"></a>
- <a name="Stroustrup05"></a>
- <a name="Stroustrup13"></a>
- <a name="Stroustrup14"></a>
- <a name="Stroustrup15"></a>
- <a name="SuttHysl04b"></a>
- <a name="SuttAlex05"></a>
- <a name="Sutter00"></a>
- <a name="Sutter02"></a>
- <a name="Sutter04"></a>
- <a name="Taligent94"></a>