Advice from B.Stroustrup's The C++ Programming
Language, 3rd Edition
Chapter 1: Suggestions for C Programmers
- Macros are almost never necessary in C++. Use const or
enum to define manifest constants, inline to
avoid function-calling overhead, templates to specify families
of functions and types, and namespaces to avoid name clashes.
- Don't declare a variable before you need it so that you can
initialize it immediately. A declaration can occur anywhere a statement
can.
- Don't use malloc. The new operator
does the same job better, and instead of realloc(), try a vector.
- Try to avoid void*, pointer arithmetic, unions, and casts, and,
except deep within the implementation of some function or class.
In most cases, a cast is an indication of a design error.
If you must use an explicit type conversion, try using one of the
"new casts" for a more precise statement of what you are trying to do.
[static_cast, reinterpret_cast, const_cast]
- Minimize the use of arrays and C-style strings. C++ standard
library string and vector classes can often be used
to simplify programming compared to traditional C style. In general,
try not to build yourself what has already been provided by the
standard library.
- To obey C linkage conventions, a C++ function must be declared to
have C linkage.
Chapter 2: A Tour of C++
- Don't panic. All will become clearer in time.
- You don't have to know every detail of C++ to write good programs.
- Focus on programming techniques, not on language features.
Chapter 3: A Tour of 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 than char*.
- If in doubt, use a range-checked vector (such as Vec).
- Prefer Vector<T>, list<T>, and map<key,value> to T[].
- When adding elements to a container, use push_back() or back_inserter().
- Use push_back() on a vector rather than realloc() 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 might change.
- Use typedefs 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 a short int or a long int.
- Prefer a double over a float or a long double.
- Prefer plain char over signed char and unsigned char.
- Avoid making unnecessary assumptions about the sizes of objects.
- Avoid unsigned arithmetic.
- View signed to unsigned and unsigned to signed conversions
with suspicion.
- View floating-point to integer conversions with suspicion.
- View conversion to a smaller type, such as int to char, with suspicion.
Chapter 5: Pointers, Arrays, and structures
- Avoid nontrivial pointer arithmetic.
- Take care not to write beyond the bounds of an array.
- Use 0 rather than NULL.
- Use vector and valarray rather than built-in(C-style) arrays.
- Use string rather than zero-terminated arrays of char.
- 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-statements.
- Don't declare a variable until you have a value to initialize it with.
- Keep comments crisp.
- Maintain a coinsistent indentation style.
- Prefer defining a member operator new() to replacing the global operator 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 or a template would be a better alternative.
Chapter 8: Namespaces and Exceptions
- Use namespace to express logical structure.
- Place every non-local name, except main(), in some namespace.
- Design a namespace so that you can conveniently use it without accidentaly 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 namespace.
- Uset 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 user's interfaces and implementers' interfaces.
- Distinguish between average users' interfaces and expert
users' interfaces.
- Avoid nonlocal objects that require run-time initialization in code
inteded for use as part of non C++ programs.
Chapter 10: Classes
- Represent concepts as classes.
- Use public data (structs) 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 objects 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 asignment).
- 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 returns.
- 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.
- 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 to the
result of your own exercises.
- Be cautious about introducing implicit conversions.
- Use member functions to express operators that require an lvalue
as 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 coexixt.
- 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 concept distinct.
Chapter 13: Templates
- Use templates to express algorithms that apply to too 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 derived clases 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 Handling
- 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 leeks 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 clkass exception.
- A library should not unilaterally terminate a program. Instead,
throw an exception and let a caller decide.
- A library shouldn't produce diagnostic output aimed at end users.
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 inavoidable.
- Prefer dynamic_cast over ,i>typeid.
- Prefer private to protected.
- 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 and iterator from
a reverse_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() or resize on a container rather than
realloc 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 a 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 or multimap when you primarily access
elements by key.
- Use the minimal set of operations to gain maximum flexibility.
- Prefer a map to a hash_map if the elements need
to be in order.
- Prefer a hash_map to a map when speed of lookup
is essential.
- Prefer a hash_map to a map 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 multimap when several values need to be kept for a single key.
- Use set or multiset 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() and mem_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() and transform() 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() and bsearch() 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 of istream_iterators
and ostream_iterators.
- Use inserters to avoid container overflow.
- Use extra checking during debugging and remove checking later
only where necessary.
- Prefer ++p to p++.
- Use uninitialized memory to improve the performance of algorithms
that require temporary 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 for rebind.
Chapter 20: Strings
- Prefer string operations to C-style string functions.
- Use strings as variables and members, rather than as base classes.
- You can pass strings as value arguments and return them by value
to let the system take care of memory management.
- Use subscripting rather than iterators when you want range checking.
- Use iterators rather than subscripting when you want to optimize
speed.
- Directly or indirectly, use substr() to read substrings
and replace() 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 ned to add characters efficiently.
- Use strings as targets of non-time-critical character input.
- Use string::npos to indicate "the rest of the string".
- If necessary, implement heavily used strings using low-level
operations (rather than using low-level data structures everywhere).
- If you use strings, catch range_error
and out_of_range somewhere.
- Be careful not to pass a char* with the value 0 to
a string function.
- Use c_str() rather to produce a C-style string representation
of a string only when you have to.
- Use isalpha(), isdigit(), etc. when you need to know the
classification of a character rather than writing your own tests
on character values.
Chapter 21: Streams
- Define << and >> for user-defined types with
values that have meaningful textural representations.
- Use parentheses when printing expressions containing
operators of low precedence.
- You don't need to modify istream or ostream
to add new << or >> operators.
- You can define a function so that it behaves as a virtual function
based on its second (or subsequent) argument.
- Use lower-level input functions such as get()
and read() only where run-time efficiency is at a premium.
- Use lower-level input functions such as get()
and read() primarily in the implementation of
higher-level input functions.
- Be careful with the termination criteria when using get(),
getline(), and read().
- Prefer manipulators to state flags for controlling I/O.
- Use exceptions to catch rare I/O errors.
- 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 #include <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 betwen formatting (iostreams)
and buffering (streambufs) 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 latter virtual).
- Use locale to localize "cultural differences".
- Use sync_with_stdio() if you mis 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 a typedef.
- Consider accumulate(), inner_product(),
partial_sum(), and adjacent_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.
- Use classes to represent concepts.
- 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 somebody 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 style.
- Use classes/concepts as primary focus for design rather than
functions/processing.
- Use classes to represent concepts.
- Use inheritance to represent hierarchical relationshipd between concepts
(only).
- Express strong guaranties 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 membership to pointers 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 the 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 may need to coexist.
- Use abstract classes to represent new interfaces to existing classses.
- Use node classes where similar classes share significant
implementation details.
- Use node classes to incrementally augment an implementation.
- Use Run-time Type Identification to obtain new interfaces for 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
control structure to be predefined.