Skip to content

Latest commit

 

History

History
121 lines (85 loc) · 4.76 KB

modern_practice.org

File metadata and controls

121 lines (85 loc) · 4.76 KB

Modern Practice

Prefer the Compiler to the Preprocessor

Use const, not macros

  • a symbol seen by the compiler provides more reasonable error messages.
  • const can be declared at the class level, and a static 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 a const 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 a const member function. This is where mutable 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 the const version by casting the object to const and cast the return value to non-=const=.
    • It is safe to cast a non-=const= object to const to call a const function; and since the object is non-=const= and mutable, it is safe to drop the const 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.

the enum Hack

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.

Use Template and inline Instead of Function-Like Macros.

  • 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.

Leak-Free in C++

Issues

  • Dangling/invalid Dereference (use after delete)
  • Null dereference
  • No leaks (delete objects after using and only once)

Goal

Ensure an object will be destroyed once it is no longer needed.

Principles

  • 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.

Scenarios

  • 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.

With C++ 98/03

Expressive Zero-Cost Abstractions

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.

Stack-based Scoping Of Resources (a.k.a RAII)

e.g. std::auto_ptr, boost::shared_ptr and boost::scoped_ptr and no client code should contain an explicit new or delete.