Effective C++

本文记录阅读Effective C++的笔记

Items

Prefer consts, enums and inlines to #defines.

For simple constants, prefer const objects or enums to #defines.

Such as code #define ASPECT_RAIO 1.653, the symbolic name ASPECT_RATIO may never be seen by compilers. Thus when something wrong, the error message may refer to 1.653, not ASPECT_RATIO.

We prefer:

1
2
3
4
5
6
7
8
9
const double AspcekRatio = 1.653;
const char* const authorName = "Scott Meyres";
const std::string authorName("scott");

class GamePlayer {
private:
  enum {NumTurns = 5};   // the enum hack
  int scores[NumTurns];   // NumTurns a symbolic name for 5, behave like #define
}

For function-like macros, prefer inline functions to #defines.

Macros have so many drawbacks, such as:

1
2
3
4
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a):(b))
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // a is incremented twice
CALL_WITH_MAX(++a, b + 10);  // a is incremented once

We prefer:

1
2
3
4
template<typename T>
inline void callWithMax(cosnt T& a, const T& b) {
  f(a > b ? a : b);
}

Use const whenever possible

Declaring something const helps compilers detect usage errors. const can be applied to objects at any scope, to function parameters and return types, and to member functions as a whole.

Compilers enforce bitwise constness, but you should program using logical constness.

The wonderful thing about const is that is specify a semantic constraint-- a particular object should not be modified.

1
2
3
const char* p= "hello"; // non-const pointer, const data
char* const p= "hello"; // const pointer, non-const data
const char* const p= "hello"; // const pointer, const data
1
2
class Rational {...};
const Rational operator* (const Rational& lhs, const Rational& rhs);

The reason that the result of operator* be a const object is we can avoid mistake if (a * b = c).

When const and non-const member functions have essentially identical implementations, code duplication can be avoided by having the non-const version cal the const version.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class TextBlock {
  public:
  ...
  const char& operator[] (std::size_t position) const {
    ...
    return text[position];
  }
  char& operator[] (std::size_t position) {
    return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]); // first add const to *this call const version [], then cast away const on return type.
  }
}

Make sure that objects are initialized before they're used.

Manually initialize objects of build-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. List data members in the initialization list in the same order they're declared in the class.

Avoid initialization order problems across translation units by replacing non-local static objects with local static objects.

Know what functions c++ silently writes and calls

Compilers may implicitly generate a class's default constructor, copy constructor, copy assignment operator, and destructor.

Explicitly disallow the use of compiler-generated functions you do not want

To prevent these functions from being generated, you must declare them yourself.

1
2
3
4
5
6
7
class HomeForSale {
public;
...
private:
HomeForSale(const HomeForSale&);  // declarations only
HomeForSale& operator= (const HomeForSale&);
}

Or create base class for inheriting:

1
2
3
4
5
6
7
8
9
class Uncopyable {
protected:
  Uncopyable(){}
  ~Uncopyable(){}
private:
  Uncopyable(const Uncopyable&);
  Uncopyable& operator=(const Uncopyable&);
}

To disallow functionality automatically provided by compilers, declare the corresponding member functions private and give no implementations. Using a base class like Uncopyable in one way to do this.

Declare destructors virtual in polymorphic base classes.

Polymorphic base classes should declare virtual destructors. If a class has any virtual functions, it should have a virtual destructor.

Classes not designed to be base classes or not designed to be used polymorphically should not declare virtual destructors.

Prevent exceptions from leaving destructors.

Destructors should never emit exceptions. If functions called in a destructor my throw, the destructor should catch any exceptions, then swallow them or terminate the program.

If class clients need to be able to react to exceptions thrown during an exception, the class should provide a regular function that performs the operation.

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.

Have assignment operators return a reference to *this

1
2
3
4
5
6
7
class Widget {
public:
  Widget& operator= (const Widget& rhs) {
    ...
    return *this;
  }
}

Have assignment operators return a reference to *this.

Handle assignment to self in operator=

1
2
3
class Widget {...};
Widget w;
w = w;

While encounter self assignment, the follow code will be wrong

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Bitmap {...};
class Widget {
  ...
  private:
  Bigmap* pb;
};

Widget& Widget::operator= (const Widget& rhs) {
  delete pb;
  pb = new Bitmap(*rhs.pb);
  return *this;
}

A good implementation can be follow:

1
2
3
4
5
6
Widget& Widget::operator= (const Widget& rhs) {
  Bitmap* pOrig = pb;
  pb = new Bigmap(*rhs.pb);
  deldte pOrig;
  return *this;
}
1
2
3
4
Widget& Widget::operator= (Widget rhs) { // rhs is a copy of the object.
  swap(rhs);
  return *this;
}

The above implementation is self-assignment-safe and exception-safe;

Make sure operator= is well-behaved when an object is assigned to itself. Techniques include comparing addresses of source and target objects, careful statement ordering, and copy-and-swap.

Make sure that any function operating on more than one object behaves correctly if two or more of the objects are the same.

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.

Provide access to raw resources in resource-managing classes.

Access may be via explicit conversion or implicit conversion. In general, explicit conversion is safer, but implicit conversion is more convenient for clients.

Store newed objects in smart pointers in standalone statements.

We prefer

1
2
std::shared_ptr<Widget> pw(new Widget);
prccesWidget(pw, priority());

Not

1
processWidget(std::shared_ptr<Widget>(new Widget), priority());

In case, the priority() function throw exception cause memory leak.

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

Don't try to return a reference when you must return an object.

Wrong;

1
2
3
4
const Rational& operator* (const Rational& lhs, const Rational& rhs) {
  Rational* result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
  return *result;
}

Prefer:

1
2
3
inline const Rational operator* (const Rational& lhs, const Rantional& rhs) {
  return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}

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 will be needed.

Declare data members private.

Declare data member 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.

Reference

  1. Effective C++
updatedupdated2021-11-062021-11-06