by Scott Meyers
I, Michael Parker, own this book and took these notes to further my own learning. If you enjoy these notes, please purchase the book!
- There is C, object-oriented C++, templates and template metaprogramming, and the STL.
- Unlike when using a
#define
, if an error occurs when using a constant, then its name is included in the error message. - For a constant pointer in the header file, declare both the pointer and the data it points to as
const
. - In-class initialization is allowed only for integral types, and only for constants.
- When using a macro, remember to parenthesize all the arguments, and beware expressions being evaluated multiple times if used as arguments.
- Using
const
is wonderful because it allows the compiler to enforce a semantic constraint. - Declaring an
iterator
const
means it isn't allowed to point to something different, but whatever it points to may be modified. - One of the hallmarks of good user-defined types is that they avoid gratuitous incompatibilities with built-in types.
- A
const
member function can overload a non-const
member function, and the former will be used onconst
objects. - Bitwise
const
is C++'s definition ofconst
. Logicalconst
is when bits in the object are changed, but in ways that the client cannot detect. - The
mutable
keyword frees non-static data members from the constraints of bitwiseconst
. - By calling
static_cast
to addconst
tothis
, and thenconst_cast
to removeconst
from the return value, the overloaded non-const
function can call theconst
one.
- Reading uninitialized values yields undefined behavior, so always initialize objects before you use them.
- Always listing every data member on the initialization list avoids having to remember which data members may go uninitialized.
- Within a class, data members are initialized in the order they're declared, and not in their order on the initialization list.
- If a non-local static object in one translation unit uses a non-local static object in another translation unit, it may be uninitialized, because their initialization order is undefined.
- Using
static
objects defined in functions eliminates this problem, and if you never call such a function, you don't even construct itsstatic
object.
- A compiler declares a copy constructor, copy assignment operator, and destructor if you don't declare them yourself, as well as a default constructor if you declare none.
- The generated destructor is not virtual unless the class inherits from a base class with a virtual destructor.
- You must define the copy constructor yourself if the class contains a reference member or a
const
member.
- Declare the copy constructor and the copy assignment operator private to prevent the compiler from generating its own version.
- To stop member and friend functions from still calling them, don't actually define them; this generates an error during the linking stage.
- When a derived class object is deleted through a pointer to the base class with a non-virtual destructor, results are undefined, but typically the derived part isn't destroyed.
- If the class isn't intended to be a base class, making the destructor virtual increases its size, as this adds a
vptr
(virtual table pointer) andvtbl
(virtual table). - The
string
class and STL container types (vector
,list
,set
, etc.) lack virtual destructors, and so should never be inherited from.
- Depending on the circumstances, if two destructors simultaneously emit exceptions, program execution either terminates or yields undefined behavior.
- One option is to terminate the program in the destructor, thereby preventing any undefined behavior.
- The second option is to swallow the exception, but only if the program can reliably continue after the exception was ignored.
- Good practice is to try and move the operation that can generate an exception to outside the destructor.
- During base class construction, virtual function calls never go down into a derived class, because an object is not of a derived class until its constructor begins execution.
- The same reasoning applies during destruction.
- You must ensure that your constructors or destructors don't call virtual functions, and that all the functions they call obey the same constraint.
- This is what all built-in types do, thereby allowing chained assignments, and it applies to all assignment operators (such as
operator+=
).
- Code that operates on references or pointers to multiple objects of the same type must consider that the objects might be the same.
- Guard against self-assignment by checking the argument's address against
this
at the top ofoperator+=
. - In many cases, a careful ordering of statements can yield code that guards against both exceptions and self-assignment.
- Another alternative that guards against both exceptions and self-assignment is the "copy and swap" technique, which is covered in Item 29.
- When you add new data members to a class, be sure to update its copy constructor and copy assignment operator accordingly.
- A copying function should copy all local data members, and also invoke the appropriate copying function in all base classes.
- A thrown exception or a premature
return
,continue
, orgoto
statement might preclude execution from eaching adelete
statement. - By putting resources inside objects like
auto_ptr
, we can rely on C++'s destructor invocation to ensure that the resources are released. - Resource Acquisition Is Initialization, or RAII, means acquiring a resource and initializing its managing object happen in the same statement.
- Copying an
auto_ptr
sets it to null. While this enforces an object being managed by only oneauto_ptr
, you cannot use them in STL containers. - There is no
auto_ptr
orshared_ptr
for dynamically allocated arrays because you should be usingvector
instead.
- For resources that are not heap-based, smart pointers like
auto_ptr
andshared_ptr
are inappropriate as resource handlers. - Policies for copying an RAII object include prohibiting copying, and reference counting, copying, and transferring ownership of the underlying resource.
- An implicit conversion function on the RAII class can make access to the raw resource easier, but this increases the chance of errors.
- Often an explicit conversion function simply named
get
is preferable, even if clients must explicitly call it each time. - Returning the raw resource violates encapsulation, but RAII classes don't exist simply to encapsulate it, but to ensure that it is released.
- Using the expression
delete
whendelete []
should be used results in undefined behavior. - The memory for an array usually includes the size of the array, so that the
delete
operator knows how many destructors to call. - Use the same form of
new
in all constructors that initialize a pointer member, or else you may use the wrong form ofdelete
. - The
string
andvector
classes largely eliminate the need to dynamically allocate an array.
- Compilers are given less leeway in reordering operations across statements than within them.
- The type system is your primary ally in preventing undesirable code from compiling.
- Restrict what can be done with a type. A common way to impose restrictions is to add
const
wherever you can. - Avoid gratuitous incompatibilities with the built-in types so that interfaces behave consistently, thereby reducing mental friction.
- Any interface that requires clients to remember to do something is prone to incorrect use, because clients can forget to do it.
- In many applications, the additional runtime costs of resource managers are unnoticeable, but the reduction in client errors will be noticed by everyone.
- Approach class design with the same care that language designers lavish on the design of built-in types.
- Good types have a natural syntax, intuitive semantics, and one or more efficient implementations.
- If you inherit from existing classes, you are constrained by their design, particularly by whether their functions are virtual or not.
- Guarantees made with respect to performance, exception safety, and resource usage impose constraints on your implementation.
- If you're defining a whole family of types, you don't want to define a new class, you want to define a new class template.
- If you're only subclassing so you can add functionality to an existing class, consider non-member functions or templates instead.
- Passing by reference eliminates the slicing problem, where passing a derived class object to a function that accepts a base class object calls the base class copy constructor.
- References are typically implemented as pointers, so passing built-in types like
int
by value is usually more efficient. - Implementers of iterators and function objects ensure that they are efficient to copy and are not subject to the slicing problem.
- Avoid passing a user-defined type by value because while its size may be small now, that is subject to change with its implementation.
- A function should never return a reference or pointer to a local object that is destroyed when the function exits.
- Returning a reference from a function like
operator*
is incorrect. Such a function must return a new object. - In some cases the construction and destruction of such a return value can be safely eliminated by the compiler.
- Many data members should be hidden, and rarely does every data member require a getter and a setter.
- Only functional interfaces makes it easy to notify other objects when variables are read or written, to verify class invariants and function pre- and postconditions, to perform synchronization in threaded environments, etc.
- Public means unencapsulated, which means an unchangeable implementation, especially for classes that are widely used.
- Protected data is as unencapsulated as public data, since changing such a data member could break all derived classes that use it, which is an unknowably large amount of code.
- The more functions that can access data, the less the data is encapsulated, and the harder it is to change the characteristics of the data.
- Unlike a member function, a non-member non-friend function doesn't increase the count of functions that can access the private parts of a class.
- Consider putting the non-member function in the same namespace as the class it operates on.
- Partitioning functionality in a namespace across multiple files promotes clean organization, and clients can freely add more functions to the namespace.
- For overloaded operators, compilers will also look at non-member functions accepting two parameters in the namespace or global scope.
- Parameters are eligible for implicit type conversion only if they are listed on the parameter list. The object on which the member function is invoked is never eligible.
- To support mixed mode arithmetic with operator overloading, make operators non-member functions accepting both operands as arguments.
- The opposite of a member function is a non-member function, not a friend function. Avoid friend functions when you can.
- If using the "pimp idiom," define a member function named
swap
that does the actual swapping, then specializestd::swap
to call that member function. - It's okay to totally specialize templates in
std
, adding new templates, classes, functions, or anything else tostd
results in undefined behavior. - In addition to the specialization of
std::swap
, write a non-member version ofswap
in the same namespace of your class. - By prefacing a call to
swap
withusing std::swap
, compilers will look for the namespace definition first, then the specialization instd
, and finally the general form.
- Postponing declaring a variable until you have its initialization arguments avoids unnecessary default constructions.
- Assigning a variable defined outside a loop is more efficient than initializing it on ever iteration, because it avoids destructing when each iteration completes.
- Casts can subvert the type system, which is there to ensure that you're not trying to perform any unsafe or nonsensical operations on objects.
- Only use
reinterpret_cast
for low-level casts in low-level code, such as from a pointer to anint
. It yields unportable results. - Use
static_cast
to force implicit conversions as well as the reverse of such conversions, with the exception ofconst
to non-const
. - Prefer the explicit, new-style casts. They are easier to search for, and their narrow purpose makes it possible for compilers to diagnose usage errors.
- Type conversions of any kind, either explicit via casts or implicit by compilers, often lead to additional code that is executed at runtime.
- Avoid making assumptions about how things are laid out in C++. Making casts based on such assumptions leads to undefined behavior.
- A
dynamic_cast
can have a significant runtime cost. If you need to perform derived class operations through a pointer or reference to the base class, explore alternative designs. - Casts should be isolated as much as possible, typically hidden inside functions whose interfaces shield callers from the work done inside.
- A data member is only as encapsulated as the most accessible function returning a reference to it.
- Returning
const
references to data members can still lead to dangling handles, which refer to parts of objects that don't exist any longer. - Functions that return a handle to an object internal are the exception and not the rule.
- Exception-safe functions don't leak resources, and don't allow data structures or objects to enter a corrupted state.
- The basic guarantee ensures that the program remains in a valid state if an exception is thrown, but that exact state may ot be predictable. The strong guarantee ensures that the state is unchanged.
- Often you can't guarantee that no exceptions are thrown, because anything that dynamically allocates memory can throw a
bad_alloc
exception. - When functions have side-effects on non-local data instead of operating exclusively on local state, it's much harder to offer the strong guarantee.
- If a system has even a single function that's not exception-safe, then the system as a whole is not exception-safe.
- A function's exception-safety guarantee is a visible part of its interface, and you should choose it as deliberately as you choose all other interface aspects.
- Compiler optimizations are typically designed for stretches of code that lack function calls, so inlining allows more optimizations.
- Inlined code can also lead to additional paging, a reduced instruction cache hit rate, and their accompanying performance penalties.
- The
inline
keyword is a request that compilers may ignore, and only the most trivial functions that are not virtual may be inlined. - Even empty constructors and destructors are unlikely to be inlined, as they implicitly call the constructors of base classes and data members.
- Debuggers have trouble with inlined functions. For example, you can't set a breakpoint in a function that isn't there.
- Instead of trying to forward-declare parts of the standard library, use the proper
#include
statements and be done with it. - You never need a class definition to declare a function using that class. Forward declare the class, and shift the burden of including its definition to clients that call the function.
- A class that employs the pimpl idiom is often called a handle class.
- If a function is declared as pure virtual in an interface class, then there is no need to include the keyword
virtual
in its declaration in the subclass. - Handle classes and interface classes decouple interfaces from implementations and thereby reduce compilation dependencies between files.
- The most important rule in object-oriented programming in C++ is that public inheritance means "is-a".
- There is no one ideal design for all software. The best design depends on what the system is expected to do, both now and in the future.
- Good interfaces prevent invalid code from compiling. Prefer design that rejects operations during compilation than one that rejects at runtime.
- A class named
Square
extendingRectangle
is a classic example of the fragile nature of class hierarchies.
- If a function in a derived class has the same name as a function in the base class, it hides all overloaded forms of that function in the base class.
- To preserve the is-a relationship of inheritance, the derived class must include a
using
declaration to inherit all overloaded forms of a function with a given name.
- Pure virtual functions must be redeclared by any concrete class that inherits them.
- A simple virtual function allows a derived class to inherit a function interface as well as an implementation.
- A pure virtual function can still have an implementations of its own. A subclass must redeclare it, but it can call down to this "default" implementation.
- A non-virtual function serves a mandatory implementation, and should never be redefined in a derived class.
- Don't blindly declare all member functions virtual, and don't blindly declare all member functions non-virtual. Consider each one individually.
- When a non-virtual member function calls a private virtual member function, subclasses can redefine the latter. This is a form of the template method design pattern.
- The only way to resolve the need for non-member functions to access the non-public parts of a class is to weaken its encapsulation.
- The
tr1::function
class can refer to anything that acts like a function and returns a type convertible to the specified type. - The "standard" strategy pattern offers the possibility that an existing strategy can be tweaked via defining a subclass.
- Non-virtual functions are statically bound, so calling one on a base class pointer or reference uses the base class implementation, and not any derived class implementation.
- A non-virtual function is an invariant over specialization for the base class, and so no derived class should try to redefine it.
- Item 7, which warns against not specifying a virtual destructor in a base class, is a special case of this item.
- An object's dynamic type is determined by the object to which it currently refers, which in turn determines how it will behave.
- Default parameters are statically bound, so invoking a virtual function defined in a derived class uses a default parameter value from the base class.
- To avoid duplication of the default parameter, use the non-virtual interface idiom, where the default parameter is only defined once in the public non-virtual function.
- Composition in the application domain expresses a has-a relationship, while in the implementation domain it expresses an is-implemented-in-terms-of relationship.
- Inappropriate subclassing violates the is-a principle, and should be replaced with composition.
- With private inheritance, you cannot assign a derived object to a base class pointer, and protected and public members in the base class are private in the derived class.
- Use composition whenever you can, and use private inheritance whenever you must.
- Use private inheritance if two classes don't have an is-a relationship, but one needs to access the protected members or redefine a virtual function in the other.
- If a class privately inherits from another and defines a virtual function, its own subclasses can redefine that function, even if they can't call it.
- If a function is defined in two base classes, then a call in the derived class to that function is always ambiguous, even if only one definition is accessible.
- Virtual inheritance prevents replicating data in the base class when multiple inheritance is used, but it's slower and creates larger objects.
- The one reasonable of multiple inheritance is to combine public inheritance of an interface with private inheritance of an implementation.
- If the only design you can come up with involves multiple inheritance, you should think a little harder.