- a symbol seen by the compiler provides more reasonable error messages.
const
can be declared at the class level, and astatic const
data member of integral or enumeration type can be initialized with an initializer right inside the class definition. If its value is required, a namespace-scope definition should be provided without an initializer.- Apply
const
whenever it makes sense to function parameters and return values, to pointers, references and iterators, to the objects referred to by pointers, iterators and references, to local variables, to member functions. - The C++ compiler understands and checks only bitness constness, that is,
whether a member function modifies this object or not. However, a
const
member function may return a pointer or reference to the internal data and aconst
object is now modifiable through unexpected approaches. Or a class has some internal cached data for optimization that is not logically part of the object, but modified in aconst
member function. This is wheremutable
comes in to allow logical constness. Program using logical constness. - In case of code duplication of a pair of
const
and non-=const= member functions, implement the non-=const= version in terms of theconst
version by casting the object toconst
and cast the return value to non-=const=.- It is safe to cast a non-=const= object to
const
to call aconst
function; and since the object is non-=const= and mutable, it is safe to drop theconst
qualifier of the return value. - The other way around for implementation is not safe: a
const
object that implicitly calls a non-=const= member function may change its state.
- It is safe to cast a non-=const= object to
In case an initializer of a constant is not allowed inside a
class definition, define an enum
in that class and use the enum as a
constant.
- Function-like macros may produce unexpected behaviors especially with operations that have side effects.
- Inline functions are type-safe with the efficiency of macros and they understand scope rules.
- Dangling/invalid Dereference (use after delete)
- Null dereference
- No leaks (delete objects after using and only once)
Ensure an object will be destroyed once it is no longer needed.
- Prefer scoped lifetime (local, members): an object’s lifetime is tied to another lifetime.
- use
unique_ptr
or a container if the object must have its own lifetime with unique ownership. It automates simple heap use. shared_ptr
reference counting- no raw pointers and explicit
delete
- break cycles with
weak_ptr
.
- Decoupled Class Member: optional part, changeable part, use unique pointers.
- strictly nested lifetime, no leaks, the data’s lifetime is bound to the containing object.
- Pimpl pointer:
const unique_ptr<T>
- the pointer cannot be changed
- strictly nested lifetime
- dynamic array member:
const unique_ptr<Data[]>
- trees:
vector<unique_ptr<Node>>
for children and a raw pointer to parent.- the parent owns the children, thus the children may hold a raw pointer wihtout worrying about dangling pointers.
- destructors are called recursively by default. They may be automatic and correct, but they may run out of stack.
- one may do destruction iteratively by navigating subtree and run constructor bottom-up; or move pointers to the nodes to be pruned to a flattened local scope heap list: a node’s children pointers are moved into this heap list so this node is has no children and there would be no double free.
- doubly linked list (a special tree): a
unique_ptr<Node>
for the next node and a raw pointer for the previous node.- still, the default destructor would recursively call the N nodes’ destructor, instead of iteratively calling them in the same stackframe.
- trees that hand out strong refs of nodes:
vector<shared_ptr<Node>>
for children- the lifetime of a node’s data is connected to the node, thus any data
returned from a node may be a
shared_ptr<Data>
but constructed with the aliasing constructor that connects the data’s lifetime with the node’s.
- the lifetime of a node’s data is connected to the node, thus any data
returned from a node may be a
Express our intent to other programmers rather than just telling the machine what to do. The compiler and various template techniques allow programmers to offer specialized versions of a given algorithm to avoid performance loss.
e.g. std::auto_ptr
, boost::shared_ptr
and boost::scoped_ptr
and no client
code should contain an explicit new
or delete
.