豆瓣🔗:

Effective C++

Scott Meyers

55 Specific Ways to Improve Your Programs and Designs

C++ 提供了4种不同又相辅相成的编程泛型(programming paradigms):

  • procedural-based
  • object-based
  • object-oriented
  • generics

Chapter 1: Accustoming Yourself to C++

1 View C++ as a federation of languages

C++: a multiparadigm programming language

  • procedural
  • object-oriented
  • functional
  • generic
  • metaprogramming

C++’s four primary sublanguages:

  • C
  • Object-Oriented C++
  • Template C++
  • The STL

内置 (C-like) 类型:pass-by-value 通常比 pass-by-reference 高效。

从 C part of C++ 移往 Object-Oriented C++, 由于 用户自定义(user-defined) 构造函数和析构函数的存在,pass-by-reference-to-const 往往更好。 ——why?

For example, pass-by-value is generally more efficient than pass-by-reference for built-in (i.e., C-like) types, but when you move from the C part of C++ to Object-Oriented C++, the existence of user-defined constructors and destructors means that pass-by-reference-to-const is usually better. This is especially the case when working in Template C++, because there, you don’t even know the type of object you’re dealing with. When you cross into the STL, however, you know that iterators and function objects are modeled on pointers in C, so for iterator and function objects in the STL, the old C pass-by-value rule applied again.

Rules for effective C++ programming vary, depending on the part of C++ you are using.

2 Prefer consts, enums, and inlines to #defines

This item might better be called “Prefer the compiler to the preprocessor”.

1
2
3
#define SPECT_RATIO 1.653
const double AspectRation=1.653; //uppercase names are usually for 
                                //macros, hence the name change
  1. define constant pointers:
1
2
3
4
5
//To define a constant char*-based string in a header file
const char * const authorName="Scott Meyers";
//string objects are generally preferable to their char*-based progenitors
//authorName if often better defined this way
const std::string authorName("Scott Meyers"); 
  1. class-specific constants: make it a static member
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//a declaration of NumTurns, not a definition
class GamePlayer{
  private:
  	static const int NumTurns=5;
  	int scores[NumTurns];
  	...
};
//a separate definition 
//If you do take the address of a class constant, 
//or if your compiler incorrectly insists on a definition even if you don't take the address
//put this in an implementation file,not a header file.
const int GamePlayer::NumTurns;
1
2
3
4
5
6
7
//put the initial value at the point definition
class CostEstimate{
  private:
  	static const double FudgeFactor;
  	...
};
const double CostEstimate::FudgeFactor=1.35;

the values of an enumerated type can be used where ints are expected:

1
2
3
4
5
6
class GamePlayer{
  private:
  	enum {NumTurns=5}; //"the enum hack"
  	int scores[NumTurns];
  	...
};

The enum hack:

It’s legal to take the address of a const, but it’s not legal to take the address of an enum and #define.

Also, through good compilers won’t set aside storage for const objects of integral types (unless you create a pointer or reference to the object), sloppy compilers may, and you may not be willing to set aside memory for such objects. Like enums, #defines never result in that kind of unnecessary memory allocation.

the enum back is a fundamental technique of template meta programming.

1
2
3
4
5
#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

use a template for an inline function:

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

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

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

3 use const whenever possible

1
2
3
4
5
char greeting[]="Hello";
char *p=greeting; //non-const pointer, non-const data
const char* p=greeting; //non-const pointer, const data
char* const p=greeting; //const pointer, non-const data
const char* const p=greeting; //const pointer, const data

When what’s pointed to is constant:

1
2
void f1(const Widget *pw); //f1 takes a pointer to a constant Widget object
void f2(Widget const *pw); //so does f2

STL iterators:

  • an iterator const: declare a pointer const (i.e., declaring a T* const pointer): the iterator isn’t allowed to point to something different, but the thing is points to may be modifed.
  • a const_iterator: an iterator that points to something that can’t be modified (i.e., the STL analogue of const T* pointer)
1
2
3
4
5
6
7
8
9
std::vector<int> vec;
...
const std::vector<int>::iterator iter=vec.begin(); //iter acts like a T* const
*iter=10; //OK, changes what iter points to
++iter; // error! iter is const

std::vector<int>::const_iterator cIter=vec.begin(); //cIter acts like a const T*
*cIter=10; //error! *cIter is const
++cIter; //fine, changes cIter
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Rational {...};
const Rational operator*(const Rational& lhs, const Rational& rhs);

//why should the result of operator* be a const object?
//if it weren't, clients would be able to commit atrocities like this:
Rational a,b,c;
...
(a*b)=c;

if(a*b=c) ... //oops, meant to do a comparison!

const Member Functions

an important feature of C++: member functions differing only in their constness can be overloaded.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class TextBlock
{
public:
    ... 
    const char &operator[](std::size_t position) const
    {
        return text[position];
    }
    char &operator[](std::size_t position)
    {
        return text[position];
    }

private:
    std::string text;
};

TextBlock tb("Hello");
std::cout << tb[0];

const TextBlock ctb("World");
std::cout << ctb[0];

void print(const TextBlock &ctb)
{
    std::cout << ctb[0];
    ...
}

std::cout << tb[0]; // fine
tb[0] = 'x';        // fine

std::cout << ctb[0]; // fine
ctb[0] = 'x';        // error!

4 Make sure the objects are initialized before they’re used

1
2
3
4
5
int x=0;
const char* text="A C-style string";

double d;
std::cin>>d; //"initializaton" by reading from an input stream
1
2
3
4
5
6
7
8
ABEntry::ABEntry(const string& name, const string& address, const list& phones)
{
  //these are all assignments, not initializations
  theName=name;
  theAddress=addres;
  thePhones=phones;
  numTimesConsulted=0;
}

It will often will be more efficient than above:

1
2
3
4
5
6
7
//memeber initialization list
ABEntry::ABEntry(const string& name, const string& address, const list& phones)
:theName(name),
 theAddress(address),
 thePhones(phone),
 numTimesConsulted(0) //these are now all initializations
{} //the ctor body is now empty

Singleton

1
2
3
4
5
6
7
class FileSystem{
  public:
  	...
    size_t numDisks() const;
  	...
};
extern FileSystem tfs;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Directory
{
public:
    Directory(params);
    ...
};
Directory::Directory(params)
{
    ... 
    size_t disks = tfs.numDisks();
    ...
}

Directory tempDir(params);

Now the importance of initialzation order becomes apparent: unless tfs is initialized before tempDir, tempDir’s constructor will attempt use tfs before it’s been initialized. But tfs and tempDir were created by non-local static objects defined in different translation units. How can you be sure that tfs will be initialized before tempDir?

修改方法:local static对象替换non-local static对象,这是Singleton模式的一种常见实现手法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class FileSystem{...};
FileSystem& tfs()
{
  static FileSystem fs;
  return fs;
}

class Directory{...};
Directory::Directory(params)
{
  ...
  size_t disks=tfs().numDisks();
  ...
}

Directory& tempDir()
{
  static Directory td;
  return td;
}

Things to Remember

  • Manually initialize objects of built-in type, because C++ only sometimes intializes 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.

Chapter 2: Constructors, Destructors, and Assignment Operators

5 Know what functions C++ silently writes and calls

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Empty{};
//the same as:
class Empty{
    public:
        Empty() {...} //default constructor
        Empty(const Empty& rhs) {...} //copy constructor
        Empty(){...} //destructor
        
        Empty& operator=(const Empty& rhs) {...} //copy assignment operator
}

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class HomeForSale {...}
HomeForSale h1;
HomeForSale h2;
HomeForSale h3(h1); //should not compile
h1=h2; //should not compile

class HomeForSale {
  public:
    ...
  private:
    ...
    HomeForSale(const HomeForSale&); //declarations only
    HomeForSale& operator=(const HomeForSale&);
}

改进方法:使用像Uncopyable这样的base class。

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

calss HomeForSale: private Uncopyable{
    ...
};

7 Declare destructors virtual in polymorphic base classes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class TimeKeeper{
    public:
        TimeKeeper();
        ~TimeKeeper();
        ...
};
class AtomicClock:public TimeKeeper{...}
class WaterClock:public TimeKeeper {...}
class WristClock:public TimeKeeper{...}

TimeKeeper* getTimeKeeper();

TimeKeeper* ptk=getTimeKeeper();
...
delete ptk;

修改方法:give the base class a virtual destructor. Deleting a derived class object will destroy the entire object, including all its derived class parts.

1
2
3
4
5
6
7
8
9
class TimeKeeper{
    public:
        TimeKeeper();
        virtual ~TimeKeeper();
        ...
};
TimeKeeper* ptk=getTimeKeeper();
...
delete ptk;

Things to Remember

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

8 Prevent exceptions from leaving destructors

9 Never call virtual functions during construction or destruction

10 Have assignment operators return a reference to *this

11 Handle assignment to self in operator=

12 Copy all parts of an object

Chapter 3: Resource Management

13 Use objects to manage resources

14 Think carefully about copying behavior in resource-managing classes

15 Provide access to raw resources in resource-managing classes

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

17 Store need objects in smart pointers in standalone statements

Chapter 4: Designs and Declarations

18 Make interfaces easy to use correctly and hard to use incorrectly

1
2
3
4
5
6
7
//bad
class Date{
    public:
        Date(int month, int day, int year);
        ...
};
Date(3,30,1995);

wrapper types(外覆类型): (一直都不知道wrapper怎么翻译,这里这样翻译)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//good
struct Day{
    explicit Day(int d):val(d){}
    int val;
};
struct Month{
    explicit Month(int m):val(m){}
    int val;
};
struct Year{
    explicit Year(int y):val(y){}
    int va;
};
class Date{
    public:
        Date(const Month& m, const Day& d, const Year& y);
        ...
};
Date(Month(3),Day(30),Year(1995)); //ok

enum不具备类型安全性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Month{
    public:
        static Month Jan() {return Month(1)};
        static Month Feb() {return Month(2)};
        ...
        static Month Dec() {return Month(12)};
        ...
    private:
        explicit Month(int m);
        ...
};
Date(Month::Mar(),Day(30),Year(1995));

C++每个STL容器都有一个名为 size() 的成员函数。

Java:

  • array:length property
  • Strings: length method
  • Lists: size method

19 Treat class design as type design

20 Prefer pass-by-reference-to-const to pass-by-value

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

22 Declare data members private

23 Prefer non-member non-friend functions to member functions

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

25 Consider support for a non-throwing swap

Chapter 5: Implementations

26 Postpone variable definitions as long as possible

27 Minimize casting

28 Avoid returning “handles” to object internals

Strive for exception-safe code

Understand the ins and outs of inlining

Minimize compilation dependencies between files

Inheritance and Object-Oriented Design

Make sure public inheritance models “is-a”

Avoid hiding inherited names

Differentiate between inheritance of interface and inheritance of implementation.

Consider alternatives to virtual functions

Never redefine an inherited non-virtual function

Never redefine a functions’s inherited default parameter value

Model “has-a” or “is-implemented-in-terms-of” through composition

Use private inheritance judiciously

use multiple inheritance judiciously

Templates and generic Programming

Understand implicit interfaces and compile-time polymorphism

Understand the two meanings of typename

Know how to access names in templatized base calsses

Factor parameter-independent code out of templates

Use member function templates to accept “all compatible types”

Define non-member functions inside templates when type conversions are desired

Use traits classes fro information about types

Be aware of template metaprogramming

Customing new and delete

Understand the behavior of the new-handler

Understand when it makes sense to replace new and delete

Adhere to convention when writing new and delete

Write placement delete if you write placement new

Miscellany

Pay attention to compiler warnings

Familiarize yourself with the standard library, including TR1

Familiarize yourself with Boost