The C++ Programming Language Bjarne Stroustrup, 3rd edition
Chapter 1 - Notes to the reader
When you program, you create a concrete representation of the ideas in your solution to some problem. Let the structure of the program reflect those ideas as directly as possible:
- If you can think of "it" as a separate idea, make it a class.
- If you can think of "it" as a separate entity, make it an object of some class.
- If two classes have a common interface, make that interface an abstract class.
- If the implementations of two classes have something significant in common, make that commonality a base class.
- If a class is a container of objects, make it a template.
- If a function implements an algorithm for a container, make it a template function implementing the algorithm for a family of containers.
- If a set of classes, templates, etc., are logically related, place them in a common namespace.
When you define either a class that does not implement a mathematical entity like a matrix or a complex number or a low-level type such as a linked list:
- Don't use global data (use members).
- Don't use global functions.
- Don't use public data members.
- Don't use friends, except to avoid [1] or [3].
- Don't put a "type field" in a class; use virtual functions.
- Don't use inline functions, except as a significant optimization.
Chapter 3 - the Standard Library
- Don't reinvent the wheel; use libraries.
- Don't believe in magic; understand what your libraries do, how they do it, and at what cost they do it.
- When you have a choice, prefer the standard library to other libraries.
- Do not think that the standard library is ideal for everything.
- Remember to
#include
the headers for the facilities you use. - Remember that standard library facilities are defined in namespace
std
. - Use
string
rather thanchar*
. - If in doubt use a range-checked vector (such as
Vec
). - Prefer
vector<T>
,list<T>
, andmap<key, value>
toT[]
. - When adding elements to a container, use
push_back()
orback_inserter()
. - Use
push_back()
on a vector rather thanrealloc()
on an array. - Catch common exceptions in
main()
.
Chapter 4 - Types and declarations
- Keep scopes small.
- Don't use the same name in both a scope and an enclosing scope.
- Declare one name (only) per declaration.
- Keep common and local names short, and keep uncommon and-nonlocal names longer.
- Avoid similar-looking names.
- Maintain a consistent naming style.
- Choose names carefully to reflect meaning rather than implementation.
- Use a
typedef
to define a meaningful name for a built-in type in cases in which the built-in type used to represent a value n-fight change. - Use
typedef
s to define synonyms for types; use enumerations and classes to define new types. - Remember that every declaration must specify a type (there is no "implicit
int
"). - Avoid unnecessary assumptions about the numeric value of characters.
- Avoid unnecessary assumptions about the size of integers.
- Avoid unnecessary assumptions about the range of floating-point types.
- Prefer a plain
int
over ashort int
or along int
. - Prefer a
double
over afloat
or along double
. - Prefer plain
char
oversigned char
andunsigned char
. - Avoid making unnecessary assumptions about the sizes of objects.
- Avoid unsigned arithmetic.
- View
signed
tounsigned
andunsigned
tosigned
conversions with suspicion. - View floating-point to integer conversions with suspicion.
- View conversions to a smaller type, such as
int
tochar
, with suspicion.
Chapter 5 - Pointers, arrays, structures
- Avoid nontrivial pointer arithmetic.
- Take care not to write beyond the bounds of an array.
- Use
0
rather thanNULL
. - Use
vector
andvalarray
rather than built-in (C-style) arrays. - Use
string
rather than zero-terminated arrays ofchar
. - Minimize use of plain reference arguments.
- Avoid
void*
except in low-level code. - Avoid nontrivial literals ("magic numbers") in code. Instead, define and use symbolic constants.
Chapter 6 - Expressions and statements
- Prefer the standard library to other libraries and to "handcrafted code".
- Avoid complicated expressions.
- If in doubt about operator precedence, parenthesize.
- Avoid explicit type conversion (casts).
- When explicit type conversion is necessary, prefer the more specific cast operators to the C-style cast.
- Use the
T(e)
notation exclusively for well-defined construction. - Avoid expressions with undefined order of evaluation.
- Avoid
goto
. - Avoid
do-while
statements. - Don't declare a variable until you have a value to initialize it with.
- Keep comments crisp.
- Maintain a consistent indentation style.
- Prefer defining a member
operator new()
[421] to replacing the globaloperator new()
. - When reading input, always consider ill-formed input.
Chapter 7 - Functions
- Be suspicious of non-const reference arguments; if you want the function to modify its arguments, use pointers and value return instead.
- Use
const
reference arguments when you need to minimize copying of arguments. - Use
const
extensively and consistently. - Avoid macros.
- Avoid unspecified numbers of arguments.
- Don't return pointers or references to local variables.
- Use overloading when functions perform conceptually the same task on different types.
- When overloading on integers, provide enough functions to eliminate common ambiguities.
- When considering the use of a pointer to function, consider whether a virtual function [36] or a template [41] would be a better alternative.
Chapter 8 - Namespaces and exceptions
- Use namespaces to express logical structure.
- Place every nonlocal name, except
main()
, in some namespace. - Design a namespace so that you can conveniently use it without accidentally gaining access to unrelated namespaces.
- Avoid very short names for namespaces.
- If necessary, use namespace aliases to abbreviate long namespace names.
- Avoid placing heavy notational burdens on users of your namespaces.
- Use the
Namespace::member
notation when defining namespace members. - Use
using namespace
only for transition or within a local scope. - Use exceptions to decouple the treatment of "errors" from the code dealing with the ordinary processing.
- Use user-defined rather than built-in types as exceptions.
- Don't use exceptions when local control structures are sufficient.
Chapter 9 - Source files and programs
- Use header files to represent interfaces and to emphasize logical structure.
#include
a header in the source file that implements its functions.- Don't define global entities with the same name and similar-but-different meanings in different translation units.
- Avoid non-inline function definitions in headers.
- Use
#include
only at global scope and in namespaces. #include
only complete declarations.- Use include guards.
#include
C headers in namespaces to avoid global names.- Make headers self-contained.
- Distinguish between users' interfaces and implementers' interfaces.
- Distinguish between average users' interfaces and expert users' interfaces.
- Avoid nonlocal objects that require run-time initialization in code intended for use as part of non-C++ programs.
Chapter 10 - Classes
- Represent concepts as classes.
- Use public data (
struct
s) only when it really is just data and no invariant is meaningful for the data members. - A concrete type is the simplest kind of class. Where applicable, prefer a concrete type over more complicated classes and over plain data structures.
- Make a function a member only if it needs direct access to the representation of a class.
- Use a namespace to make the association between a class and its helper functions explicit.
- Make a member function that doesn't modify the value of its object a
const
member function. - Make a function that needs access to the representation of a class but needn't be called for a specific object a
static
member function. - Use a constructor to establish an invariant for a class.
- If a constructor acquires a resource, its class needs a destructor to release the resource.
- If a class has a pointer member, it needs copy operations (copy constructor and copy assignment).
- If a class has a reference member, it probably needs copy operations (copy constructor and copy assignment).
- If a class needs a copy operation or a destructor, it probably needs a constructor, a destructor, a copy assignment, and a copy constructor.
- Check for self-assignment in copy assignments.
- When writing a copy constructor, be careful to copy every element that needs to be copied (beware of default initializers).
- When adding a new member to a class, always check to see if there are user-defined constructors that need to be updated to initialize the member.
- Use enumerators when you need to define integer constants in class declarations.
- Avoid order dependencies when constructing global and namespace objects.
- Use first-time switches to minimize order dependencies.
- Remember that temporary objects are destroyed at the end of the full expression in which they are created.
Chapter 11 - Operator overloading
- Define operators primarily to mimic conventional usage.
- For large operands, use
const
reference argument types. - For large results, consider optimizing the return.
- Prefer the default copy operations if appropriate for a class.
- Redefine or prohibit copying if the default is not appropriate for a type.
- Prefer member functions over nonmembers for operations that need access to the representation.
- Prefer nonmember functions over members for operations that do not need access to the representation.
- Use namespaces to associate helper functions with "their" class.
- Use nonmember functions for symmetric operators.
- Use
()
for subscripting multidimensional arrays. - Make constructors that take a single "size argument"
explicit
. - For non-specialized uses, prefer the standard
string
(Chapter 20) to the result of your own exercises. - Be cautious about introducing implicit conversions.
- Use member functions to express operators that require an lvalue as its left-hand operand.
Chapter 12 - Derived classes
- Avoid type fields.
- Use pointers and references to avoid slicing.
- Use abstract classes to focus design on the provision of clean interfaces.
- Use abstract classes to minimize interfaces.
- Use abstract classes to keep implementation details out of interfaces.
- Use virtual functions to allow new implementations to be added without affecting user code.
- Use abstract classes to minimize recompilation of user code.
- Use abstract classes to allow alternative implementations to coexist.
- A class with a virtual function should have a virtual destructor.
- An abstract class typically doesn't need a constructor.
- Keep the representations of distinct concepts distinct.
Chapter 13 - Templates
- Use templates to express algorithms that apply to many argument types.
- Use templates to express containers.
- Provide specializations for containers of pointers to minimize code size.
- Always declare the general form of a template before specializations.
- Declare a specialization before its use.
- Minimize a template definition's dependence on its instantiation contexts.
- Define every specialization you declare.
- Consider if a template needs specializations for C-style strings and arrays.
- Parameterize with a policy object.
- Use specialization and overloading to provide a single interface to implementations of the same concept for different types.
- Provide a simple interface for simple cases and use overloading and default arguments to express less common cases.
- Debug concrete examples before generalizing to a template.
- Remember to
export
template definitions that need to be accessible from other translation units. - Separately compile large templates and templates with nontrivial context dependencies.
- Use templates to express conversions but define those conversions very carefully.
- Where necessary, constrain template arguments using a
constraint()
member function. - Use explicit instantiation to minimize compile time and link time.
- Prefer a template over derived classes when run-time efficiency is at a premium.
- Prefer a derived class over a template if adding new variants without recompilation is important.
- Prefer a template over derived classes when no common base can be defined.
- Prefer a template over derived classes when built-in types and structures with compatibility constraints are important.
Chapter 14 - Exception handlling
- Use exceptions for error handling.
- Don't use exceptions where more local control structures will suffice.
- Use the "resource allocation is initialization" technique to manage resources.
- Not every program needs to be exception safe.
- Use "resource allocation is initialization" and exception handlers to maintain invariants.
- Minimize the use of
try
blocks. Use "resource acquisition is initialization" instead of explicit handler code. - Not every function needs to handle every possible error.
- Throw an exception to indicate failure in a constructor.
- Avoid throwing exceptions from copy constructors.
- Avoid throwing exceptions from destructors.
- Have
main()
catch and report all exceptions. - Keep ordinary code and error-handling code separate.
- Be sure that every resource acquired in a constructor is released when throwing an exception in that constructor.
- Keep resource management hierarchical.
- Use exception-specifications for major interfaces.
- Beware of memory leaks caused by memory allocated by new not being released in case of an exception.
- Assume that every exception that can be thrown by a function will be thrown.
- Don't assume that every exception is derived from class
exception
. - A library shouldn't unilaterally terminate a program. Instead, throw an exception and let a caller decide.
- A library shouldn't produce diagnostic output aimed at an end user. Instead, throw an exception and let a caller decide.
- Develop an error-handling strategy early in a design.
Chapter 15 - Class hierarchies
- Use ordinary multiple inheritance to express a union of features.
- Use multiple inheritance to separate implementation details from an interface.
- Use a
virtual
base to represent something common to some, but not all, classes in a hierarchy. - Avoid explicit type conversion (casts).
- Use
dynamic_cast
where class hierarchy navigation is unavoidable. - Prefer
dynamic_cast
overtypeid
. - Prefer
private
toprotected
. - Don't declare data members
protected
. - If a class defines
operator delete
(), it should have a virtual destructor. - Don't call virtual functions during construction or destruction.
- Use explicit qualification for resolution of member names sparingly and preferably use it in overriding functions.
Chapter 16 - Library organization and containers
- Use standard library facilities to maintain portability.
- Don't try to redefine standard library facilities.
- Don't believe that the standard library is best for everything.
- When building a new facility, consider whether it can be presented within the framework offered by the standard library.
- Remember that standard library facilities are defined in namespace
std
. - Declare standard library facilities by including its header, not by explicit declaration.
- Take advantage of late abstraction.
- Avoid fat interfaces.
- Prefer algorithms with reverse iterators over explicit loops dealing with reverse order.
- Use
base()
to extract aniterator
from areverse_iterator
. - Pass containers by reference.
- Use iterator types, such as
list<char>::iterator
, rather than pointers to refer to elements of a container. - Use
const
iterators where you don't need to modify the elements of a container. - Use
at()
, directly or indirectly, if you want range checking. - Use
push_back()
orresize()
on a container rather thanrealloc()
on an array. - Don't use iterators into a resized vector.
- Use
reserve()
to avoid invalidating iterators. - When necessary, use
reserve()
to make performance predictable.
Chapter 17 - Standard containers
- By default, use
vector
when you need a container. - Know the cost (complexity, big-O measure) of every operation you use frequently.
- The interface, implementation, and representation of a container are distinct concepts. Don't confuse them.
- You can sort and search according to a variety of criteria.
- Do not use a C-style string as a key unless you supply a suitable comparison criterion.
- You can define a comparison criteria so that equivalent, yet different, key values map to the same key.
- Prefer operations on the end of a sequence (
back
-operations) when inserting and deleting elements. - Use
list
when you need to do many insertions and deletions from the front or the middle of a container. - Use
map
ormultimap
when you primarily access elements by key. - Use the minimal set of operations to gain maximum flexibility.
- Prefer a
map
to ahash_map
if the elements need to be kept in order. - Prefer a
hash_map
to amap
when speed of lookup is essential. - Prefer a
hash_map
to amap
if no less-than operation can be defined for the elements. - Use
find()
when you need to check if a key is in an associative container. - Use
equal_range()
to find all elements of a given key in an associative container. - Use
multimap
when several values need to be kept for a single key. - Use
set
ormultiset
when the key itself is the only value you need to keep.
Chapter 18 - Algorithms and function objects
- Prefer algorithms to loops.
- When writing a loop, consider whether it could be expressed as a general algorithm.
- Regularly review the set of algorithms to see if a new application has become obvious.
- Be sure that a pair of iterator arguments really do specify a sequence.
- Design so that the most frequently-used operations are simple and safe.
- Express tests in a form that allows them to be used as predicates.
- Remember that predicates are functions and objects, not types.
- You can use binders to make unary predicates out of binary predicates.
- Use
mem_fun()
andmem_fun_ref()
to apply algorithms on containers. - Use
ptr_fun()
when you need to bind an argument of a function. - Remember that
strcmp()
differs from == by returning 0 to indicate "equal". - Use
for_each()
andtransform()
only when there is no more-specific algorithm for a task. - Use predicates to apply algorithms using a variety of comparison and equality criteria.
- Use predicates and other function objects so as to use standard algorithms with a wider range of meanings.
- The default
==
and < on pointers are rarely adequate for standard algorithms. - Algorithms do not directly add or subtract elements from their argument sequences.
- Be sure that the less-than and equality predicates used on a sequence match.
- Sometimes, sorted sequences can be used to increase efficiency and elegance.
- Use
qsort()
andbsearch()
for compatibility only.
Chapter 19 - Iterators and allocators
- When writing an algorithm, decide which kind of iterator is needed to provide acceptable efficiency and express the algorithm using the operators supported by that kind of iterator (only).
- Use overloading to provide more-efficient implementations of an algorithm when given as arguments iterators that offer more than minimal support for the algorithm.
- Use
iterator_traits
to express suitable algorithms for different iterator categories. - Remember to use
++
between accesses ofistream_iterators
andostream_iterators
. - Use inserters to avoid container overflow.
- Use extra checking during debugging and remove checking later only where necessary.
- Prefer
++p
top++
. - Use uninitialized memory to improve the performance of algorithms that expand data structures.
- Use temporary buffers to improve the performance of algorithms that require temporary data structures.
- Think twice before writing your own allocator.
- Avoid
malloc()
,free()
,realloc()
, etc. - You can simulate a
typedef
of a template by the technique used forrebind
.
Chapter 20 - Strings
- Prefer
string
operations to C-style string functions. - Use
string
s as variables and members, rather than as base classes. - You can pass
string
s as value arguments and return them by value to let the system take care of memory management. - Use
at()
rather than iterators or[]
when you want range checking. - Use iterators and
[]
rather thanat()
when you want to optimize speed. - Directly or indirectly, use
substr()
to read substrings andreplace()
to write substrings. - Use the
find()
operations to localize values in a string (rather than writing an explicit loop). - Append to a
string
when you need to add characters efficiently. - Use
string
s as targets of non-time-critical character input. - Use
string::npos
to indicate "the rest of the string". - If necessary, implement heavily-used
string
s using low-level operations (rather than using low-level data structures everywhere). - If you use
string
s, catchrange_error
andout_of_range
somewhere. - Be careful not to pass a
char*
with the value 0 to a string function. - Use
c_str
to produce a C-style string representation of astring
only when you have to. - Use
isalpha()
,isdigit()
, etc., when you need to know the classification of a character rather that writing your own tests on character values.
Chapter 21 - Streams
- Define
<<
and>>
for user-defined types with values that have meaningful textual representations. - Use parentheses when printing expressions containing operators of low precedence.
- You don't need to modify
istream
orostream
to add new<<
and>>
operators. - You can define a function so that it behaves as a
virtual
function based on its second (or subsequent) argument. - Remember that by default
>>
skips whitespace. - Use lower-level input functions such as
get()
andread()
primarily in the implementation of higher-lever input functions. - Be careful with the termination criteria when using
get()
,getline()
, andread()
. - Prefer manipulators to state flags for controlling I/O.
- Use exceptions to catch rare I/O errors (only).
- Tie streams used for interactive I/O.
- Use sentries to concentrate entry and exit code for many functions in one place.
- Don't use parentheses after a no-argument manipulator.
- Remember to
#include <iomanip></iomanip>
when using standard manipulators. - You can achieve the effect (and efficiency) of a ternary operator by defining a simple function object.
- Remember that
width
specifications apply to the following I/O operation only. - Remember that
precision
specifications apply to all following floating-point output operations. - Use string streams for in-memory formatting.
- You can specify a mode for a file stream.
- Distinguish sharply between formatting (
iostream
s) and buffering (streambuf
s) when extending the I/O system. - Implement nonstandard ways of transmitting values as stream buffers.
- Implement nonstandard ways of formatting values as stream operations.
- You can isolate and encapsulate calls of user-defined code by using a pair of functions.
- You can use
in_avail()
to determine whether an input operation will block before reading. - Distinguish between simple operations that need to be efficient and operations that implement policy (make the former
inline
and the lattervirtual
). - Use
locale
to localize "cultural differences". - Use
sync_with_stdio(x)
to mix C-style and C++-style I/O and to disassociate C-style and C++-style I/O. - Beware of type errors in C-style I/O.
Chapter 22 - Numerics
- Numerical problems are often subtle. If you are not 100% certain about the mathematical aspects of a numerical problem, either take expert advice or experiment.
- Use
numeric_limits
to determine properties of built-in types. - Specialize
numeric_limits
for user-defined scalar types. - Use
valarray
for numeric computation when run-time efficiency is more important than flexibility with respect to operations and element types. - Express operations on part of an array in terms of slices rather than loops.
- Use compositors to gain efficiency through elimination of temporaries and better algorithms.
- Use
std::complex
for complex arithmetic. - You can convert old code that uses a complex class to use the
std::complex
template by using atypedef
. - Consider
accumulate()
,inner_product()
,partial_sum()
, andadjacent_difference()
before you write a loop to compute a value from a list. - Prefer a random-number class for a particular distribution over direct use of
rand()
. - Be careful that your random numbers are sufficiently random.
Chapter 23 - Development and design
- Know what you are trying to achieve.
- Keep in mind that software development is a human activity.
- Proof by analogy is fraud.
- Have specific and tangible aims.
- Don't try technological fixes for sociological problems.
- Consider the longer term in design and in the treatment of people.
- There is no lower limit to the size of programs for which it is sensible to design before starting to code.
- Design processes to encourage feedback.
- Don't confuse activity for progress.
- Don't generalize beyond what is needed, what you have direct experience with, and what can be tested.
- Represent concepts as classes.
- There are properties of a system that should not be represented as a class.
- Represent hierarchical relationships between concepts as class hierarchies.
- Actively search for commonality in the concepts of the application and implementation and represent the resulting more general concepts as base classes.
- Classifications in other domains are not necessarily useful classifications in an inheritance model for an application.
- Design class hierarchies based on behavior and invariants.
- Consider use cases.
- Consider using CRC cards.
- Use existing systems as models, as inspiration, and as starting points.
- Beware of viewgraph engineering.
- Throw a prototype away before it becomes a burden.
- Design for change, focusing on flexibility, extensibility, portability, and reuse.
- Focus on component design.
- Let each interface represent a concept at a single level of abstraction.
- Design for stability in the face of change.
- Make designs stable by making heavily-used interfaces minimal, general, and abstract.
- Keep it small. Don't add features "just in case".
- Always consider alternative representations for a class. If no alternative representation is plausible, the class is probably not representing a clean concept.
- Repeatedly review and refine both the design and the implementation.
- Use the best tools available for testing and for analyzing the problem, the design, and the implementation.
- Experiment, analyze, and test as early as possible and as often as possible.
- Don't forget about efficiency.
- Keep the level of formality appropriate to the scale of the project.
- Make sure that someone is in charge of the overall design.
- Document, market, and support reusable components.
- Document aims and principles as well as details.
- Provide tutorials for new developers as part of the documentation.
- Reward and encourage reuse of designs, libraries, and classes.
Chapter 24 - Design and programming
- Evolve use towards data abstraction and object-oriented programming.
- Use C++ features and techniques as needed (only).
- Match design and programming styles.
- Use classes/concepts as a primary focus for design rather than functions/processing.
- Use classes to represent concepts.
- Use inheritance to represent hierarchical relationships between concepts (only).
- Express strong guarantees about interfaces in terms of application-level static types.
- Use program generators and direct-manipulation tools to ease well-defined tasks.
- Avoid program generators and direct-manipulation tools that do not interface cleanly with a general-purpose programming language.
- Keep distinct levels of abstraction distinct.
- Focus on component design.
- Make sure that a virtual function has a well-defined meaning and that every overriding function implements a version of that desired behavior.
- Use public inheritance to represent "is a" relationships.
- Use membership to represent "has a" relationships.
- Prefer direct membership over a pointer to a separately-allocated object for expressing simple containment.
- Make sure that the "uses" dependencies are understood, non-cyclic wherever possible, and minimal.
- Define invariants for all classes.
- Explicitly express preconditions, postconditions, and other assertions as assertions (possibly using
Assert()
). - Define interfaces to reveal the minimal amount of information needed.
- Minimize an interface's dependencies on other interfaces.
- Keep interfaces strongly typed.
- Express interfaces in terms of application-level types.
- Express an interface so that a request could be transmitted to a remote server.
- Avoid fat interfaces.
- Use
private
data and member functions wherever possible. - Use the
public/protected
distinction to distinguish between the needs of designers of derived classes and general users. - Use templates for generic programming.
- Use templates to parameterize an algorithm by a policy.
- Use templates where compile-time type resolution is needed.
- Use class hierarchies where run-time type resolution is needed.
Chapter 25 - Roles of classes
- Make conscious decisions about how a class is to be used (both as a designer and as a user).
- Be aware of the tradeoffs involved among the different kinds of classes.
- Use concrete types to represent simple independent concepts.
- Use concrete types to represent concepts where close-to-optimal efficiency is essential.
- Don't derive from a concrete class.
- Use abstract classes to represent interfaces where the representation of objects might change.
- Use abstract classes to represent interfaces where different representations of objects need to coexist.
- Use abstract classes to represent new interfaces to existing types.
- Use node classes where similar concepts share significant implementation details.
- Use node classes to incrementally augment an implementation.
- Use Run-Time Type Identification to obtain interfaces from an object.
- Use classes to represent actions with associated state.
- Use classes to represent actions that need to be stored, transmitted, or delayed.
- Use interface classes to adapt a class for a new kind of use (without modifying the class).
- Use interface classes to add checking.
- Use handles to avoid direct use of pointers and references.
- Use handles to manage shared representations.
- Use an application framework where an application domain allows for the control structure to be predefined.