豆瓣🔗:
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
|
- 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");
|
- 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
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
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
Author
Anjana
LastMod
2022-09-30
License
原创文章,如需转载请注明作者和出处。谢谢!