[NOTE] Effective C++

24 minute read

CH1: Accustoming Yourself to C++

Item 1: View C++ as a federation of languages.

  • C++ was just C with some object-oriented features tacked on.
  • View C++ as a federation of related languages: C, Object-Oriented C++, Template C++, and The STL.
  • Rules for effective C++ programming vary, depending on the part of C++ you are using.

Item 2: Prefer consts, enums, and inlines to #defines.

  • Prefer the compiler to the preprocessor as #define may be treated as if it’s not part of the language per se.
    • The symbolic name may be removed by the preprocessor before the source code ever gets to a compiler.
    • Not yet time to retire the preprocessor but should definitely give it long and frequent vacations.
  • Prefer const objects or enums to #defines for simple constants.
  • Prefer inline functions to #defines for function-like macros.
  • Use of the constant may yield smaller code than using a #define.
    • Define constant pointers and prefer string objects to their char*-based progenitors.
    • Make a class-specific constant a member and make it a static member to ensure there’s at most one copy.
  • The enum hack takes advantage of the fact that the values of an enumerated type can be used where ints are expected.
    • Behave in some ways more like a #define than a const does, e.g., illegal to take the address of an enum.
    • Be pragmatic, e.g., a fundamental technique of template metaprogramming.

Item 3: Use const whenever possible.

  • const can be applied to objects at any scope, to function parameters and return types, and to member functions as a while.
  • Declaring something const allows specifying a semantic constraint and helps compilers detect usage errors.
  • Declaring an iterator const is like declaring a pointer const, e.g., declaring a T* const pointer.
  • Declaring a parameter or local object const saves from annoying errors.
    • e.g., “I meant to type ‘==’ but I accidently typed ‘=’” mistake.
  • There are two prevailing notions regarding const member functions: bitwise constness (a.k.a. physical constness) and logical constness.
    • bitwise constness believes that if and only if it doesn’t modify any of the object’s non-static data members.
    • logical constness believes that it might modify some of the bits in the object on which it’s invoked, but only in ways that clients cannot detect.
    • mutable frees non-static data members from the constraints of bitwise constness.
    • Compilers enforce bitwise constness, but you should program using logical constness.
  • Code duplication can be avoided by having the non-const version call the const version.
    • Worth knowing the technique of implementing a non-const member function in terms of its const twin.
    • e.g., have the non-const version of operator[] call the const one (static_cast) and that brings to casting away constness (const_cast).

Item 4: Make sure that objects are initialized before they’re used.

  • Manually initialize objects of built-in type, because C++ only sometimes initializes them itself.
  • In a constructor, prefer use of the member initialization list to assignment inside the body of the constructor.
    • Data members of an object are initialized before the body of a constructor is entered.
    • Data members that are const or are references must be initialized and can’t be assigned.
    • List data members in the initialization list in the same order they’re declared in the class.
  • The relative order of initialization of non local static objects defined in different translation units is undefined.
    • A translation unit is the source code giving rise to a single object file. It’s basically a single source file, plus all of its #include files.
  • Avoid initialization order problems across translation units by replacing non-local static objects with local static objects.
    • Move each non-local static object into its own function, where it’s declared static.
    • Local static objects are initialized when the object’s definition is first encountered during a call to that function.
  • The reference-returning functions are excellent candidates for inlining, especially if they’re called frequently.
    • They are problematic in multithreaded systems. Solved by manually invoking all them during the single-threaded startup portion of the program.

CH2: Constructors, Destructors, and Assignment Operators

Item 5: Know what functions C++ silently writes and calls.

  • Compilers may implicitly generate a class’s default constructor, copy constructor, copy assignment operator, and destructor.
    • All these functions are both public and inline.
    • All these functions are generated only if they are needed.
  • If the copy assignment in a class containing a reference member should be supported, the function should be defined manually.
    • Compilers behave similarly for classes containing const members.
  • Compilers reject implicit copy assignment operators in derived classes that inherit from base classes declaring the copy assignment operator private.

Item 6: Explicitly disallow the use of compiler-generated functions you do not want.

  • To disallow functionality automatically provided by compilers, declare the corresponding member functions private and give no implementations.
    • If the function is called inadvertently in a member or a friend function, the linker will complain.
  • To move the link-time error up to compile time, let the class inherit from Uncopyable class.
    • Inheritance from Uncopyable needn’t be public.
    • Uncopyable’s destructor needn’t be virtual.

Item 7: Declare destructors virtual in polymorphic base classes.

  • The purpose of virtual functions is to allow customization of derived class implementations.
  • Polymorphic base classes should declare virtual destructors. If a class has any virtual functions, it should have a virtual destructor.
    • When a derived class object is deleted through a pointer to a base class with a non-virtual destructor, results are undefined.
  • Classes not designed to be base classes or not designed to be used polymorphically should not declare virtual destructors.

Item 8: Prevent exceptions from leaving destructors.

  • Destructors should never emit exceptions.
  • If functions called in a destructor may throw, a reasonable idea would be to create a resource-managing class.
    • Terminate the program, typically by calling abort as it may forestall undefined behavior.
    • Swallow the exception, a bad idea as it may suppress important information.
  • A better solution is to design the interface within the resource-managing class so that its clients have an opportunity to react to problems that may arise.
    • If there may be a need to handle that exception, the exception has to come from some non-destructor function.

Item 9: Never call virtual functions during construction or destruction.

  • Don’t call virtual functions during construction or destruction, because such calls will never go to a more derived class than that of the currently executing constructor or destructor.
  • Compensate by having derived classes pass necessary construction information up to base class constructors.

*Item 10: Have assignment operators return a reference to this.

  • Have assignment operators return a reference to *this.
    • The convention is followed by all the built-in types as well as by all the types in the standard library.

Item 11: Handle assignment to self in operator=.

  • Make sure operator= is well-behaved when an object is assigned to itself.
    • Solution 1: compare addresses of source and target objects.
    • Solution 2: order statements carefully.
    • Solution 3: apply “copy and swap`” technique.
  • Make sure that any function operating on more than one object behaves correctly if two or more of the objects are the same.

Item 12: Copy all parts of an object.

  • Copying functions should be sure to copy all of an object’s data members and all of its base class parts.
  • Don’t try to implement one of the copying functions in terms of the other. Instead, put common functionality in a third function that both call.

CH3: Resource Management

Item 13: Use objects to manage resources.

  • To prevent resource leaks, use Resource Acquisition Is Initialization (RAII) objects that acquire resources in their constructors and release them in their destructors.
  • Two commonly useful RAII classes are tr1::shared_ptr and auto_ptr. tr1::shared_ptr is usually the better choice, because its behavior when copied is intuitive. Copying an auto_ptr sets it to null.

Item 14: Think carefully about copying behavior in resource-managing classes.

  • Copying an RAII object entails copying the resource it manages, so the copying behavior of the resources determines the copying behavior of the RAII object.
    • Solution 1: prohibit copying.
    • Solution 2: reference-count the underlying resource.
    • Solution 3: copy the underlying resource.
    • Solution 4: transfer ownership of the underlying resource.

Item 15: Provide access to raw resources in resource-managing classes.

  • APIs often require access to raw resources, so each RAII class should offer a way to get at the resource it manages.
  • Access may be via explicit conversion or implicit conversion. In general, explicit conversion is safer, but implicit conversion is more convenient for clients.

Item 16: Use the same form in corresponding uses of new and delete.

  • If you use [] in a new expression, you must use [] in the corresponding delete expression. If you don’t use [] in a new expression, you mustn’t use [] in the corresponding delete expression.

Item 17: Store newed objects in smart pointers in standalone statements.

  • Store newed objects in smart pointers in standalone statements. Failure to do this can lead to subtle resource leaks when exceptions are throw.
    • An exception can intervene between the time a resource is created and the time that resource is turned over to a resource-managing object.

CH4: Designs and Declarations

Item 18: Make interfaces easy to use correctly and hard to use incorrectly.

  • Good interfaces are easy to use correctly and hard to use incorrectly. You should strive for these characteristics in all your interfaces.
  • Ways to facilitate correct use include consistency in interfaces and behavioral compatibility with built-in types.
  • Ways to prevent errors include creating new types, restricting operations on types, constraining object values, and eliminating client resource management responsibilities.
  • tr1::shared_ptr supports custom deleters. This prevents the cross-DLL problem, can be used to automatically unlock mutexes, etc.

Item 19: Treat class design as type design.

  • Class design is type design. Before defining a new type, be sure to consider all the issues discussed in this item.
    • How should objects of your new type be created and destroyed?
    • How should object initialization differ from object assignment?
    • What does it mean for objects of your new type to be passed by value?
    • What are the restrictions on legal values for your new type?
    • Does your new type fit into an inheritance graph?
    • What kind of type conversions are allowed for your new type?
    • What operators and functions make sense for the new type?
    • What standard functions should be disallowed?
    • Who should have access to the members of your new type?
    • What is the “undeclared interface” of your new type?
    • How general is your new type?
    • Is a new type really what you need?

Item 20: Prefer pass-by-reference-to-const to pass-by-value.

  • Prefer pass-by-reference-to-const over pass-by-value. It’s typically more efficient and it avoids the slicing problem.
  • The rule doesn’t apply to built-in types and STL iterator and function object types. For them, pass-by-value is usually appropriate.

Item 21: Don’t try to return a reference when you must return an object.

  • Never return a pointer or reference to a local stack object, a reference to a heap-allocated object, or a pointer or reference to a local static object if there is a chance that more than one such object will be needed.

Item 22: Declare data members private.

  • Declare data members private. It gives clients syntactically uniform access to data, affords fine-grained access control, allows invariants to be enforced, and offers class authors implementation flexibility.
  • protected is no more encapsulated than public.

Item 23: Prefer non-member non-friend functions to member functions.

  • Prefer non-member non-friend functions to member functions. Doing so increases encapsulation, packaging flexibility, and functional extensibility.

Item 24: Declare non-member functions when type conversions should apply to all parameters.

  • If you need type conversions on all parameters to a function (including the one that would otherwise be pointed to by the this pointer), the function must be a non-member.

Item 25: Consider support for a non-throwing swap.

  • Provide a swap member function when std::swap would be inefficient for your type. Make sure your swap doesn’t throw exceptions.
  • If you offer a member swap, also offer a non-member swap that calls the member. For classes (not templates), specialize std::swap, too.
     1class WidgetImpl;
     2
     3class Widget {
     4 public:
     5  // ...
     6  void swap(Widget& other) {
     7    using std::swap;
     8    swap(plmpl, other.plmpl);
     9  }
    10  // ...
    11
    12 private:
    13  WidgetImpl* plmpl;
    14};
    15
    16namespace std {
    17template <>
    18void swap<Widget>(Widget& a, Widget& b) {
    19  a.swap(b);
    20}
    21}  // namespace std
    
  • When calling swap, employ a using declaration for std::swap, then call swap without namespace qualification.
    1template <typename T>
    2void doSomething(T& obj1, T& obj2) {
    3  using std::swap;
    4  // ...
    5  swap(obj1, obj2);
    6  // ...
    7}
    
  • It’s fine to totally specialize std templates for user-defined types, but never try to add something completely new to std.
    • Though C++ allows partial specialization of class templates, it doesn’t allow it for function templates.
      1// invalid code
      2namespace std {
      3template <typename T>
      4void swap(Widget<T>& a, Widget<T>& b) {
      5  a.swap(b);
      6}
      7}  // namespace std
      

CH5: Implementations

Item 26: Postpone variable definitions as long as possible.

  • Postpone variable definitions as long as possible. It increases program clarity and improves efficiency.
  • Unless you know the following two points, you should default to defining the variable used only inside the loop.
    • Assignment is less expensive than a constructor-destructor pair.
    • Dealing with a performance-sensitive part of your code.
    1for (int i = 0; i < n; ++i) {
    2  Widget w(some value dependent on i);
    3  // ...
    4}
    

Item 27: Minimize casting.

  • Avoid casts whenever practical, especially dynamic_casts in performance-sensitive code. If a design requires casting, try to develop a cast-free alternative.
    • Use containers that store pointers to derived class objects directly, thus eliminating the need to manipulate such objects through base class interfaces.
      1typedef std::vector<std::tr1::shared_ptr<SpecialWindow>> VPSW;
      2VPSW winPtrs;
      3// ...
      4for (VPSW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter) {
      5  (*iter)->blink();
      6}
      
    • Provide virtual functions in the base class that let you do what you need.
       1class Window {
       2 public:
       3  virtual void blink() {}
       4  // ...
       5};
       6
       7class SpecialWindow : public Window {
       8 public:
       9  virtual void blink() {}
      10  // ...
      11};
      
  • When casting is necessary, try to hide it inside a function. Clients can then call the function instead of putting casts in their own code.
  • Prefer C++-style casts to old-style casts. They are easier to see, and they are more specific about what they do.
     1// C-style casts look like this:
     2(T)expression
     3// Function-style casts use this syntax:
     4T(expression)
     5
     6// C++-style casts look like this:
     7const_cast<T>(expression)
     8dynamic_cast<T>(expression)
     9reinterpret_cast<T>(expression)
    10static_cast<T>(expression)
    

Item 28: Avoid returning “handles” to object internals.

  • Avoid returning handles (references, pointers, or iterators) to object internals. Not returning handles increases encapsulation, helps const member functions act const, and minimizes the creation of dangling handles.
    • Dangling handles are handles that refer to parts of objects that don’t exist any longer.
      1class Rectangle;
      2class GUIObject;
      3
      4const Rectangle boundingBox(const GUIObject& obj);
      5
      6GUIObject* pgo;
      7// at the end of the statement, boundingBox's return value - temp - will be
      8// destroyed, and that will indirectly lead to the destruction of temp's Points
      9const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft());
      

Item 29: Strive for exception-safe code.

  • Exception-safe functions leak no resources and allow no data structures to become corrupted, even when exceptions are thrown.
    • Functions offering the basic guarantee promise that if an exception is thrown, everything in the program remains in a valid state.
    • Functions offering the strong guarantee promise that if an exception is thrown, the state of the program is unchanged.
    • Functions offering the nothrow guarantee promise never to throw exceptions.
  • The strong guarantee can often be implemented via copy-and-swap, but the strong guarantee is not practical for all functions.
  • A function can usually offer a guarantee no stronger than the weakest guarantee of the functions it calls.

Item 30: Understand the ins and outs of inlining.

  • Limit most inlining to small, frequently called functions. This facilitates debugging and binary upgradability, minimizes potential code bloat, and maximizes the chances of greater program speed.
  • Don’t declare function templates inline just because they appear in header files.

Item 31: Minimize compilation dependencies between files.

  • The general idea behind minimizing compilation dependencies is to depend on declarations instead of definitions. Two approaches based on this idea are Handle classes and Interface classes.
    • Classes like Person that employ the pimpl idiom are often called Handle classes. One way is to forward all their function calls to the corresponding implementation classes and have those classes do the real work.
      1#include "Person.h"
      2#include "PersonImpl.h"
      3
      4Person::Person(const std::string& name, const Data& birthday, const Address& addr)
      5    : pImpl(new PersonImpl(name, birthday, addr)) {}
      6
      7std::string Person::name() const { return pImpl->name(); }
      
    • Clients of an Interface classes must have a way to create new objects. They typically do it by calling a function that plays the role of the constructor for the derived classes that are actually instantiated. Such functions are typically called factory functions or virtual constructors.
       1// Interface class:
       2class Person {
       3 public:
       4  static std::shared_ptr<Person> create(const std::string& name, const Date& birthday,
       5                                        const Address& addr);
       6  virtual ~Person();
       7
       8  virtual std::string name() const = 0;
       9  virtual std::string birthDate() const = 0;
      10  virtual std::string address() const = 0;
      11  // ...
      12};
      13
      14std::shared_ptr<Person> Person::create(const std::string& name, const Date& birthday,
      15                                       const Address& addr) {
      16  return std::shared_ptr<Person>(new RealPerson(name, birthday, addr));
      17}
      18
      19// Clients use them like this:
      20int main() {
      21  std::string name;
      22  Date dateOfBirth;
      23  Address address;
      24
      25  std::shared_ptr<Person> pp(Person::create(name, dateOfBirth, address));
      26  // ...
      27  std::cout << pp->name() << "was born on" << pp->birthDate() << " and now lives at"
      28            << pp->address();
      29  // ...
      30}
      
  • Library header files should exist in full and declaration-only forms. This applies regardless of whether templates are involved.

CH6: Inheritance and Object-Oriented Design

Item 32: Make sure public inheritance models “is-a.”

  • Public inheritance means “is-a”. Everything that applies to base classes must also apply to derived classes, because every derived class object is a base class object.

Item 33: Avoid hiding inherited names.

  • Names in derived classes hide names in base classes. Under public inheritance, this is never desirable.
  • To make hidden names visible again, employ using declarations or forwarding functions.
     1// with using declarations:
     2class Base {
     3 private:
     4  int x;
     5
     6 public:
     7  virtual void mf1() = 0;
     8  virtual void mf1(int);
     9  virtual void mf2();
    10  void mf3();
    11  void mf3(double);
    12  // ...
    13};
    14
    15class Derived : public Base {
    16 public:
    17  using Base::mf1;
    18  using Base::mf3;
    19  virtual void mf1();
    20  void mf3();
    21  void mf4();
    22  // ...
    23};
    24
    25// with forwarding functions:
    26class Base {
    27 public:
    28  virtual void mf1() = 0;
    29  virtual void mf1(int);
    30  // ...
    31};
    32
    33class Derived : private Base {
    34 public:
    35  virtual void mf1() { Base::mf1(); }
    36  // ...
    37};
    
  • Another use for inline forwarding functions is to work around ancient compilers that (incorrectly) don’t support using declarations to import inherited names into the scope of a derived class.

Item 34: Differentiate between inheritance of interface and inheritance of implementation.

  • Inheritance of interface is different from inheritance of implementation. Under public inheritance, derived classes always inherit base class interfaces.
  • Pure virtual functions specify inheritance of interface only.
  • Simple (impure) virtual functions specify inheritance of interface plus inheritance of a default implementation.
  • Non-virtual functions specify inheritance of interface plus inheritance of a mandatory implementation.

Item 35: Consider alternatives to virtual functions.

  • Alternatives to virtual functions include the non-virtual interface idiom (NVI idiom) and various forms of the Strategy design pattern. The NVI idiom is itself an example of the Template Method design pattern.
  • A disadvantage of moving functionality from a member function to a function outside the class is that the non-member function lacks access to the class’s non-public members.
  • tr1::function objects act like generalized function pointers. Such objects support all callable entities compatible with a given target signature.

Item 36: Never redefine an inherited non-virtual function.

  • Never redefine an inherited non-virtual function.

Item 37: Never redefine a function’s inherited default parameter value.

  • Never redefine an inherited default parameter value, because default parameter values are statically bound, while virtual functions - the only functions you should be redefining - are dynamically bound.

Item 38: Model “has-a” or “is-implemented-in-terms-of” through composition.

  • Composition has meanings completely different from that of public inheritance.
  • In the application domain, composition means has-a. In the implementation domain, it means is-implemented-in-terms-of.
     1// "has-a"
     2class Address {};
     3class PhoneNumber {};
     4
     5class Person {
     6 public:
     7  // ...
     8
     9 private:
    10  std::string name;
    11  Address address;
    12  PhoneNumber voiceNumber;
    13  PhoneNumber faxNumber;
    14};
    15
    16// "is-implemented-in-terms-of"
    17template <class T>
    18class Set {
    19 public:
    20  bool member(const T& item) const;
    21  void insert(const T& item);
    22  void remove(const T& item);
    23  std::size_t size() const;
    24
    25 private:
    26  std::list<T> rep;
    27};
    

Item 39: Use private inheritance judiciously.

  • Private inheritance means is-implemented-in-terms of. It’s usually inferior to composition, but it makes sense when a derived class needs access to protected base class members or needs to redefine inherited virtual functions.
     1class Timer {
     2 public:
     3  explicit Timer(int tickFrequency);
     4  virtual void onTick() const;
     5  // ...
     6};
     7
     8class Widget : private Timer {
     9 private:
    10  virtual void onTick() const;
    11  // ...
    12};
    
  • Unlike composition, private inheritance can enable the empty base optimization, This can be important for library developers who strive to minimize object sizes.

Item 40: Use multiple inheritance judiciously.

  • Multiple inheritance is more complex than single inheritance. It can lead to new ambiguity issues and to the need for virtual inheritance.
     1class BorrowableItem {
     2 public:
     3  void checkOut();
     4  // ...
     5};
     6
     7class ElectronicGadget {
     8 private:
     9  bool checkOut();
    10  // ...
    11};
    12
    13class MP3Player : public BorrowableItem, public ElectronicGadget {};
    14
    15MP3Player mp;
    16mp.checkOut();                  // ambiguous
    17mp.BorrowableItem::checkOut();  // ah, that checkOut...
    
  • Virtual inheritance imposes costs in size, speed, and complexity of initialization and assignment. It’s most practical when virtual base classes have no data.
    • C++ performs the replication by default when it comes to “deadly MI diamond”.
    • The responsibility for initializing a virtual base is borne by the most derived class in the hierarchy.
  • Multiple inheritance does have legitimate uses. One scenario involves combining public inheritance from an Interface class with private inheritance from a class that helps with implementation.

CH7: Templates and Generic Programming

Item 41: Understand implicit interfaces and compile-time polymorphism.

  • Both classes and templates support interfaces and polymorphism.
  • For classes, interfaces are explicit and centered on function signatures. Polymorphism occurs at runtime through virtual functions.
  • For template parameters, interfaces are implicit and based on valid expressions. Polymorphism occurs during compilation through template instantiation and function overloading resolution.
     1class Class1 {
     2 public:
     3  void interfaceFunc();
     4  void otherFunc1();
     5};
     6
     7class Class2 {
     8 public:
     9  void interfaceFunc();
    10  void otherFunc2();
    11};
    12
    13// implicit interfaces:
    14template <typename T>
    15class UseClass {
    16 public:
    17  void run(T& obj) { obj.interfaceFunc(); }
    18};
    

Item 42: Understand the two meanings of typename.

  • When declaring template parameters, class and typename are interchangeable.
  • If the parser encounters a nested dependent name in a template, it assumes that the name is not a type unless you tell it otherwise.
    • Names in a template that are dependent on a template parameter are called dependent names.
    1template <typename C>
    2void print2nd(const C& container) {
    3  if (container.size() >= 2) {
    4    typename C::const_iterator iter(container.begin());
    5    // ...
    6  }
    7}
    
  • Use typename to identify nested dependent type names, except in base class lists or as a base class identifier in a member initialization list.
    1template <typename T>
    2class Derived : public Base<T>::Nested {
    3 public:
    4  explicit Derived(int x) : Base<T>::Nested(x) {
    5    typename Base<T>::Nested temp;
    6    // ...
    7  }
    8};
    

Item 43: Know how to access names in templatized base classes.

  • In derived class templates, refer to names in base class templates via a “this->” prefix, via using declarations, or via an explicit base class qualification.

Item 44: Factor parameter-independent code out of templates.

  • Templates generate multiple classes and multiple functions, so any template code not dependent on a template parameter causes bloat.
  • Bloat due to non-type template parameters can often be eliminated by replacing template parameters with function parameters or class data members.
  • Bloat due to type parameters can be reduced by sharing implementations for instantiation types with identical binary representations.

Item 45: Use member function templates to accept “all compatible types.”

  • Use member function templates to generate functions that accept all compatible types.
     1template <typename T>
     2class SmartPtr {
     3 public:
     4  template <typename U>
     5  SmartPtr(const SmartPtr<U>& other) : heldPtr(other.get()) {
     6    // ...
     7  }
     8
     9  T* get() const {return heldPtr};
    10
    11 private:
    12  T* heldPtr;
    13};
    
  • If you declare member templates for generalized copy construction or generalized assignment, you’ll still need to declare the normal copy constructor and copy assignment operator, too.
     1template <class T>
     2class shared_ptr {
     3 public:
     4  shared_ptr(shared_ptr const& r);
     5  template <class Y>
     6  shared_ptr(shared_ptr<Y> const& r);
     7  shared_ptr& operator=(shared_ptr const& r);
     8  template <class Y>
     9  shared_ptr& operator=(shared_ptr<Y> const& r);
    10  // ...
    11};
    

Item 46: Define non-member functions inside templates when type conversions are desired.

  • Implicit type conversion functions are never considered during template argument deduction.
  • Class templates don’t depend on template argument deduction (that process applies only to function templates).
  • When writing a class template that offers functions related to the template that support implicit type conversions on all parameters, define those functions as friends inside the class template.
     1template <typename T>
     2class Rational {
     3 public:
     4  // ...
     5  friend const Rational operator*(const Rational& lhs, const Rational& rhs);
     6  // inside a class template, the name of the template can be used as shorthand for
     7  // the template and its parameters
     8  // friend const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs);
     9};
    10
    11template <typename T>
    12const Rational<T> operator*(const Rational<T>& lhs, Rational<T>& rhs) {
    13  // ...
    14}
    

Item 47: Use traits classes for information about types.

  • Traits classes make information about types available during compilation. They’re implemented using templates and template specializations.
  • In conjunction with overloading, traits classes make it possible to perform compile-time if...else tests on types.

Item 48: Be aware of template metaprogramming.

  • Template meta programming (TMP) can shift work from runtime to compile-time, thus enabling earlier error detection and higher runtime performance.
    • Compilers are obliged to make sure that all source code is valid, even if it’s not executed.
  • TMP can be used to generate custom code based on combinations of policy choices, and ti can also be used to avoid generating code inappropriate for particular types.

CH8: Customizing new and delete

Item 49: Understand the behavior of the new-handler.

  • set_new_handler allows you to specify a function to be called when memory allocation requests cannot be satisfied.
     1class Widget {
     2 public:
     3  static std::new_handler set_new_handler(std::new_handler p) throw();
     4  static void* operator new(std::size_t size) throw(std::bad_alloc);
     5
     6 private:
     7  static std::new_handler currentHandler;
     8};
     9
    10std::new_handler Widget::currentHandler = 0;
    11
    12std::new_handler Widget::set_new_handler(std::new_handler p) throw() {
    13  std::new_handler oldHandler = currentHandler;
    14  currentHandler = p;
    15  return oldHandler;
    16}
    
  • To ensure that the original new-handler is always reinstated, class treats the global new-handler as a resource and uses resource-managing objects to prevent resource leaks.
     1// "mixin-style" base class for class-specific set_new_handler support
     2template <typename T>
     3class NewHandlerSupport {
     4 public:
     5  static std::new_handler set_new_handler(std::new_handler p) thorw();
     6  static void* operator new(std::size_t size) throw(std::alloc);
     7  // ...
     8
     9 private:
    10  static std::new_handler currentHandler;
    11};
    12
    13class Widget : public NewHandlerSupport<Widget> {
    14  // ...
    15};
    
  • Nothrow new is of limited utility, because it applies only to memory allocation; associated constructor calls may still throw exceptions.

Item 50: Understand when it makes sense to replace new and delete.

  • There are many valid reasons for writing custom versions of new and delete.
    • To detect usage errors.
    • To collect statistics about the use of dynamically allocated memory.
    • To increase the speed of allocation and deallocation.
    • To reduce the space overhead of default memory management.
    • To compensate for suboptimal alignment in the default allocator.
    • To cluster related objects near one another.
    • To obtain unconventional behavior.

Item 51: Adhere to convention when writing new and delete.

  • operator new should contain an infinite loop trying to allocate memory, should call the new-handler if it can’t satisfy a memory request, and should handle requests for zero bytes. Class-specific versions should handle requests for larger blocks than expected.
     1class Base {
     2 public:
     3  static void* operator new(std::size_t size) throw(std::bad_alloc);
     4  static void operator delete(void* rawMemory, std::size_t size) throw();
     5  // ...
     6};
     7
     8class Derived : public Base {
     9  // ...
    10};
    11
    12void* Base::operator new(std::size_t size) throw(std::bad_alloc) {
    13  if (size != sizeof(Base)) {
    14    return ::operator new(size);
    15  }
    16
    17  // ...
    18}
    
  • operator delete should do nothing if passed a pointer that is null. Class-specific version should hander blocks that are larger than expected.
     1void Base::operator delete(void* rawMemory, std::size_t size) throw() {
     2  if (rawMemory == 0) {
     3    return;
     4  }
     5
     6  if (size != sizeof(Base)) {
     7    ::operator delete(rawMemory);
     8    return;
     9  }
    10
    11  // ...
    12
    13  return;
    14}
    

Item 52: Write placement delete if you write placement new.

  • When you write a placement version of operator new, be sure to write the corresponding placement version of operator delete. If you don’t, your program may experience subtle, intermittent memory leaks.
    • The runtime system looks for a version of operator delete that takes the same number and types of extra arguments as operator new, and, if it finds it, that’s the one it calls.
    • Placement delete is called only if an exception arises from a constructor call that’s coupled to a call to a placement new. Applying delete to a pointer never yields a call to a placement version of delete.
  • When you declare placement versions of new and delete, be sure not to unintentionally hide the normal versions of those functions.
     1class StandardNewDeleteForms {
     2 public:
     3  // normal new/delete
     4  static void* operator new(std::size_t size) throw(std::bad_alloc) { 
     5    return ::operator new(size); 
     6  }
     7  static void operator delete(void* pMemory) throw() { 
     8    ::operator delete(pMemory); 
     9  }
    10
    11  // placement new/delete
    12  static void* operator new(std::size_t size, void* ptr) throw() {
    13    return ::operator new(size, ptr);
    14  }
    15  static void operator delete(void* pMemory, void* ptr) throw() { 
    16    ::operator delete(pMemory, ptr); 
    17  }
    18
    19  // nothrow new/delete
    20  static void* operator new(std::size_t size, const std::nothrow_t& nt) throw() {
    21    return ::operator new(size, nt);
    22  }
    23  static void operator delete(void* pMemory, const std::nothrow_t& nt) throw() {
    24    ::operator delete(pMemory, nt);
    25  }
    26};
    27
    28class Widget : public StandardNewDeleteForms {
    29 public:
    30  using StandardNewDeleteForms::operator new;
    31  using StandardNewDeleteForms::operator delete;
    32
    33  static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc);
    34  static void operator delete(void* pMemory, std::ostream& logStream) throw();
    35  // ...
    36};
    

CH9: Miscellany

Item 53: Pay attention to compiler warnings.

  • Take compiler warnings seriously, and strive to compile warning-free at the maximum warning level supported by your compilers.
  • Don’t become dependent on compiler warnings, because different compilers warn about different things. Porting to a new compiler may eliminate warning messages you’ve come to rely on.

Item 54: Familiarize yourself with the standard library, including TR1.

  • The primary standard C++ library functionality consists of the STL, iostreams, and locales. The C89 standard library is also included.
  • TR1 adds support for smart pointers (e.g., tr1::shared_ptr), generalized function pointers (tr1::function), hash-based containers, regular expressions, and 10 other components.
  • TR1 itself is only a specification. To take advantage of TR1, you need an implementation. One source for implementations of TR1 components is Boost.

Item 55: Familiarize yourself with Boost.

  • Boost is a community and web site for the development of free, open source, peer-reviewed C++ libraries. Boost plays an influential role in C++ standardization.
  • Boost offers implementations of many TR1 components, but it also offers many other libraries, too.