Note of book Effective CPP 3rd Edition

Part 1: accustoming yourself to CPP

Item1: 将C++视为多多语言的一个联合

将C++视为四个子集:

a. C语言部分: 包括C语言的的基础,比如语句块,声明,预处理,内建数据结构,数组,指针等。但还增加了C语言没有的函数模板,异常抛出,函数重载等。

b. 面向对象的C++部分: 类(包含构造和析构),封装,集成,多态,虚函数(动态绑定)等。是面向对象的程序设计中的重要概念。

c. C++模板: 模板编程很强大,是一种新的编程范式,元编程模板。

d. STL: STL(Standard Template Library),是非常特殊的模板库。容器,迭代器,算法,函数对象。

 

Item2: Prefer consts, enums, and inline to #define.

#define ASPECT_RATIO 1.653

预处理器其会用1.653完全的替换到ASPECT_RATIO,所以之后编译器编译之后,符号表(symbol table)中就不会有ASPECT_RATIO,所以如果出错会难以调试。

 

推荐使用常量的方式来定义ASPECT_RATIO:

const double AspectRatio = 1.653; 

 

字符串定义为:

const char * const authorName = "Scott Meyers";

更合适的方式:

const std::string authorName("Scott Meyers");

 

通过static限制在类中,则最多只有一份拷贝,且类内部的常量不需要进行定义就能够直接进行声明。

class GamePlayer {
private:
static const int NumTurns = 5; // constant declaration
int scores[NumTurns]; // use of constant
...
};

当需要获取类内部常量的地址或者编译器错误的要去需要进行定义的时候可以用下面的格式:

const int GamePlayer::NumTurns; // definition of NumTurns; see
// below for why no value is given

上面的语句要放到implementation file中,而不是header file中。Because the
initial value of class constants is provided where the constant is
declared (e.g., NumTurns is initialized to 5 when it is declared), no ini-
tial value is permitted at the point of definition.

 

对于:compilers insist on knowing the size of the array during compilation,推荐:

class GamePlayer {
private:
enum { NumTurns = 5 }; // “the enum hack” — makes
// NumTurns a symbolic name for 5
int scores[NumTurns]; // fine
...
};

 

对于:

// call f with the maximum of a and b
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))

应该使用inline函数替换宏定义:

template<typename T> // because we don’t
inline void callWithMax(const T& a, const T& b) // know what T is, we
{ // pass by reference-to-
      f(a > b ? a : b); // const — see Item 20
}

 

宏定义的主要用处:

a. #include remains essential

b. #ifdef / #ifndef continue to play important roles in controlling compilation. 

 

Item3: Use const whenever possible

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

对应的记忆规则:

If the word const appears to the left of the asterisk, what’s pointed to is constant; if the
word const appears to the right of the asterisk, the pointer itself is con-
stant; if const appears on both sides, both are constant.

 

关于两种不同形式的书写方式:

When what’s pointed to is constant, some programmers list const
before the type. Others list it after the type but before the asterisk.
There is no difference in meaning, so the following functions take the
same parameter type:
void f1(const Widget * pw); // f1 takes a pointer to a
// constant Widget object
void f2(Widget const * pw); // so does f2

 

对于STL iterators:

std::vector<int> vec;
...
const std::vector<int>::iterator iter =     // iter acts like a T * const
vec.begin();
* iter = 10;                                            // OK, changes what iter points to
++iter;                                                  // error! iter is const

 

//迭代器就是为了不断的指向下一个元素,所以指向是可以改变的,所以const_iterator 的

//意思应该是迭代器指向的元素的值不可改变。
std::vector<int>::const_iterator cIter =    // cIter acts like a const T *
vec.begin();
* cIter = 10;                                             // error! * cIter is const
++cIter;                                                   // fine, changes cIter

。。。。。。。。。。

 

 

 

Item4:  Make sure that objects are initialized before thty're used

List data members in the initialization list in the same order they’re declared in the class.

 

 

 

 

Part 2:  Constructors, Destructors and Assignment Operators

Item5: 明白C++会默认调用哪些函数

当写下:

class Empty{};

的时候,编译器会添加默认构造函数,默认拷贝构造函数,析构函数,拷贝赋值运算符的重载,而且它们都是public和inline的

class Empty {
public:
Empty() { ... }                                                   // default constructor
Empty(const Empty& rhs) { ... }                       // copy constructor
~Empty() { ... }                                                 // destructor — see below, 注意不是virtual的
// for whether it’s virtual
Empty& operator=(const Empty& rhs) { ... }    // copy assignment operator
};

默认构造函数: 如果没有在类中显示声明任意的构造函数,则编译器会生成一个不带参数的默认构造函数,否则就不会生成。

析构函数: 是非virtual的。

copy constructor 和 copy assignment operator:

copy constructor and the copy assignment operator, the compiler-generated versions simply copy each non-static data mem-
ber of the source object over to the target object.

而如果成员变量有引用或const的时候(如下),

private:
       std::string& nameValue; // this is now a reference
       const T objectValue; // this is now const

这个时候如果使用到默认的copy assignment operator编译是通不过的。报错信息为:

           error: non-static reference member 'std::string& CopyTest::m_str', can't use default assignment operator,

           error: non-static const member 'const int CopyTest::m_a', can't use default assignment operator

 

最后一点:

compilers reject implicit copy assignment operators in derived classes that inherit from base classes declaring the copy assignment operator private . After all, compiler-generated copy assignment operators for derived classes are supposed to handle base class parts, too.

 

Item 6: 显示禁止编译器生成默认拷贝构造函数,赋值函数从而造成唯一对象的拷贝。

copy constructor and the copy assignment operator are declared private and are not defined.

将拷贝构造函数和赋值运算符声明为private而不进行定义

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

};

 

方法二:

声明一个空的类如下

class Uncopyable {
protected:                            // allow construction
              Uncopyable() {}     // and destruction of
              ~Uncopyable() {}   // derived objects...
private:
              Uncopyable(const Uncopyable&);    // ...but prevent copying
              Uncopyable& operator=(const Uncopyable&);

};

然后继承这个类即可:

class HomeForSale: private Uncopyable {              // class no longer
                            ...                                                 // declares copy ctor or
};                                                                             // copy assign. operator

 

Item 7: 在多态型的基类中,要将析构函数声明为virtual

class TimeKeeper {
public:
          TimeKeeper();
          ~TimeKeeper();
          ...
};
class AtomicClock: public TimeKeeper { ... };
class WaterClock: public TimeKeeper { ... };
class WristWatch: public TimeKeeper { ... };

 

TimeKeeper * getTimeKeeper(); // returns a pointer to a dynamic-
                                                   // ally allocated object of a class
                                                   // derived from TimeKeeper

使用:

TimeKeeper * ptk = getTimeKeeper(); // get dynamically allocated object
                                                            // from TimeKeeper hierarchy
...                                                         // use it
delete ptk;                                           // release it to avoid resource leak

 

因为TimeKeeper中析构函数 ~TimeKeeper()不是virtual的,想要用基类的指针来释放派生类的资源。而根据C++的行为定义:

C++ specifies that when a derived class object is deleted through a pointer to a base class with a non-virtual destructor, results are undefined.

所以这里会造成“partially destroyed"内存泄露的情况,这难以调试,极不容易发现。

解决方法: give the base class a virtual destructor.

class TimeKeeper {
public:
TimeKeeper();
virtual ~TimeKeeper();
...
};
TimeKeeper * ptk = getTimeKeeper();
...
delete ptk;              // now behaves correctly

 

因为虚函数指针vptr (“virtual table pointer”)和虚函数表vtbl (“virtual table”)的原因,类中声明了virtual函数后,大小会增加。

所以对于不用于作基类和多态的类,也不要将析构函数声明为virtual.

 

对于找不到虚函数的纯虚类,还可以将其析构函数声明为纯虚函数。

class AWOV {                             // AWOV = “Abstract w/o Virtuals”
public:
          virtual ~AWOV() = 0;       // declare pure virtual destructor
};

AWOV::~AWOV() {}                  // definition of pure virtual dtor

 

故:

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

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

 

Item 8: 不要直接在析构函数里面抛出异常

析构函数抛出异常后因为会停止执行析构函数(program
execution either terminates or yields undefined behavior),所以可能会早成程序中分配的资源得不到释放,造成内存泄露。

处理方式:

方法一:Terminate the program if close throws, typically by calling abort :

This is a reasonable option if the program cannot continue to run
after an error is encountered during destruction. It has the advan-
tage that if allowing the exception to propagate from the destructor
would lead to undefined behavior, this prevents that from happen-
ing. That is, calling abort may forestall undefined behavior.

避免了undefined behavior
DBConn::~DBConn()
{
      try { db.close(); }
      catch (...) {
            make log entry that the call to close failed;
            std::abort(); 
      }

}

方法二:Swallow the exception arising from the call to close

Sometimes,however, swallowing exceptions is preferable to running the risk of premature program termination or undefined behavior. For this to be a viable option, the program must be able to reliably continue
execution even after an error has been encountered and ignored.

DBConn::~DBConn()
{
        try { db.close(); }
             catch (...) {
             make log entry that the call to close failed;
         }
}

方法三:让调用这主动释放资源并捕获异常。

class DBConn {
public:
...
void close() // new function for
{   // client use
    db.close();
    closed = true;
}

~DBConn()
{
    if (!closed) {
        try { // close the connection
       db.close(); // if the client didn’t
    }
       catch (...) { // if closing fails,
       make log entry that call to close failed; // note that and
       ... // terminate or swallow
       }
   }

}

private:
DBConnection db;
bool closed;
};

 

Item 9: 绝对不要在构造函数或析构函数里调用虚函数(直接或间接)

Don’t call virtual functions during construction or destruction, be-
cause such calls will never go to a more derived class than that of
the currently executing constructor or destructor.

因为:

Because base class constructors execute before derived class con-
structors(析构的时候和此顺序相反), derived class data members have not been initialized when
base class constructors run
. If virtual functions called during base
class construction went down to derived classes, the derived class
functions would almost certainly refer to local data members, but
those data members would not yet have been initialized. That would
be a non-stop ticket to undefined behavior and late-night debugging
sessions. Calling down to parts of an object that have not yet been ini-
tialized is inherently dangerous, so C++ gives you no way to do it.

 

Item 10: 赋值操作符需要返回*this(即左值的引用)

assignment returns a reference to
its left-hand argument, and that’s the convention you should follow
when you implement assignment operators for your classes,包括 +=, -=, * =, etc等操作符:

class Widget 
{
public:
    ...
    Widget& operator=(const Widget& rhs) // return type is a reference to
    {         // the current class
        ...
        return * this; // return the left-hand object
    }
    ...
};

 

Item 11: Operator=中,对自己进行赋值时的处理

1. Make sure operator= is well-behaved when an object is assigned to itself. Techniques include

    a. comparing addresses of source and target objects (if (this == &rhs)   return * this; )

Widget& Widget::operator=(const Widget& rhs)
{
    if (this == &rhs) return * this; // identity test: if a self-assignment,
    // do nothing
    delete pb;
    pb = new Bitmap( * rhs.pb);
    return * this;
}

    b. careful statement ordering

Widget& Widget::operator=(const Widget& rhs)
{
    Bitmap * pOrig = pb; // remember original pb
    pb = new Bitmap( * rhs.pb); // point pb to a copy of rhs’s bitmap
    delete pOrig; // delete the original pb
    return * this;
}

    c. copy-and- swap .

 

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

 

Item 12: 拷贝对象的时,要拷贝对象的所有部分。

对于copy constructor 和 copy assignment operator,可能存在:

a. 类中增加了新的成员变量,而两个函数没有做对应的更新。

b. 在派生类的拷贝构造函数中忘记了对基类的成员变量的拷贝。

故:

Copying functions should be sure to copy all of an object’s data
members
and all of its base class parts.

 

在为了避免copy constructor 和 copy assignment operator的代码重复的问题上:

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.

 

Part 3: Resource Management

Item 13: 使用对象来管理资源

by putting resources inside objects, we can rely on C++’s automatic destructor invocation to make
sure that the resources are released.

策略:

a.  To prevent resource leaks, use RAII objects that acquire resources
in their constructors and release them in their destructors
.(即RAII机制)

b. Two commonly useful RAII classes are std::shared_ptr and auto_ptr(C++ 11中的std::unique_ptr) .
std::shared_ptr is usually the better choice, because its behavior when
copied is intuitive. Copying an auto_ptr sets it to null.

关于几种不同的智能指针,可以参考:

https://blog.csdn.net/qingdujun/article/details/74858071

 

Item 14: 仔细思考资源管理类的拷贝行为。

a. Copying an RAII object entails copying the resource it manages, so
the copying behavior of the resource determines the copying behav-
ior of the RAII object.


b. Common RAII class copying behaviors are disallowing copying and
performing reference counting
, but other behaviors are possible.

 

Item 15: 注意是否需要支持在资源管理类中对管理资源的直接访问。

1. APIs often require access to raw resources, so each RAII class
should offer a way to get at the resource it manages.

2. Access may be via explicit conversion or implicit conversion. In gen-
eral, explicit conversion is safer, but implicit conversion is more con-
venient for clients.

 

Item 16: new 与delete使用的时候需要配对

std::string * stringPtr1 = new std::string;
std::string * stringPtr2 = new std::string[100];
...
delete stringPtr1;                 // delete an object
delete [] stringPtr2;              // delete an array of objects

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

 

 

Item 17: 在“standalone statements”语句中,将new出来的对象存放在智能指针中

Store new ed objects in smart pointers in standalone statements.
Failure to do this can lead to subtle resource leaks when exceptions
are thrown.

 

 

Part 4: Designs and Declarations

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

 

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(太多了,不一一列出来了).

 

Item 20: 函数传参时,尽量采用传递const引用的方式,而不要用传值的方式

a. Prefer pass-by-reference-to- const over pass-by-value. It’s typically
more efficient and it avoids the slicing problem.

b. The rule doesn’t apply to built-in types and STL iterator and func-
tion object types
. For them, pass-by-value is usually appropriate.

 

Item 21: 当函数必须返回值的时候,不要返回引用

错误1:

const Rational& operator * (const Rational& lhs, // warning! bad code!
const Rational& rhs)
{
        Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
        return result;  //Rational为栈上变量,返回后即释放,所有返回引用没有意义
}

错误2:

const Rational& operator * (const Rational& lhs, // warning! more bad code!
const Rational& rhs) 
{
        Rational * result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
        return * result;  //Rational为栈上变量,返回后即释放,所有返回引用没有意义,还不能释放new的资源
}

错误3:

const Rational& operator * (const Rational& lhs, // warning! yet more
const Rational& rhs) // bad code!
{
         static Rational result; // static object to which a
         // reference will be returned
        result = ... ; // multiply lhs by rhs and put the
        // product inside result
        return result;
}

bool operator==(const Rational& lhs, // an operator==
const Rational& rhs); // for Rationals
Rational a, b, c, d;
...
if ((a * b) == (c * d)) {
        do whatever’s appropriate when the products are equal;   //一直都会为True,因为static变量只会在代码中存在一份,后面的

                                                                                                    //c*d的值会覆盖掉前面a*b的值,所以一直都相等。等效于:

                                                                                                    //if (operator==(operator * (a, b), operator * (c, d)))
} else {
        do whatever’s appropriate when they’re not;
}

正确的写法应该返回一个对象,而不是引用

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

Never return a pointer or reference to a local stack object, a refer-
ence 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: 将数据成员声明为private

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

2. protected is no more encapsulated than public.   From an encapsulation point
of view, there are really only two access levels: private (which offers
encapsulation) and everything else (which doesn’t).

 

Item 23: 当需要对类中的多个public函数用一个函数进行封装时(封装的逻辑关系和类不紧密),这时更倾向于将这个函数放在类的外面,他是non-member, non-friend的。

class WebBrowser {
public:
    ...
    void clearCache();
    void clearHistory();
    void removeCookies();
    ...
};

void clearBrowser(WebBrowser& wb)
{
    wb.clearCache();
    wb.clearHistory();
    wb.removeCookies();
}

而不应该用:
class WebBrowser {
public:
    ...
    void clearEverything(); // calls clearCache, clearHistory,
    // and removeCookies
    ...
};

更符合C++的做法,是将这个函数和类一起放在namespace里面

namespace WebBrowserStuff {
    class WebBrowser { ... };
    void clearBrowser(WebBrowser& wb);
    ...
}

Putting all convenience functions in multiple header files — but one
namespace — also means that clients can easily extend the set of con-
venience functions. All they have to do is add more non-member non-
friend functions to the namespace.

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

 

 

Part 5:

item 26: 尽可能的延迟变量的定义

Postpone variable definitions as long as possible. It increases pro-
gram clarity and improves program efficiency.

 

Part 6: 继承和面向对象的设计

item 32:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值