Effective C++--笔记

来源:互联网 发布:招商银行外汇期权软件 编辑:程序博客网 时间:2024/04/30 17:04

第一章    Accustoming Yourself to C++

1           View C++ as a federation of language

                  (1).              C part

                  (2).              Object-Oriented C++

                  (3).              Template C++

                  (4).              The STL

2           Prefer const, enum, and inline to #define

                  (1).              #define ABC 1.653  ->  const double ABC = 1.653
const支持类型检查
如果该常量未被使用,后者将占用空间,不过这通常是多虑
不过需要注意:定义字符串常量需要双const
const char * const authorName = “Scott Meyers”;
不使用const std::string authorName(“Scott Meyers”);可能后者使用动态内存

                  (2).              类作用域常量const
const支持作用域
class GamePlayer
{
private:
    static const int NumTurns = 3; //这里是声明,并非定义
    int scores[NumTurns];
}
有些编译器无法支持这种声明方式,需要如下修改
class GamePlayer
{
private:
    static const int NumTurns ; //这里也是声明,并非定义
    ……
}
const double GamePlayer::NumTurns = 3; //定义

                  (3).              枚举类型
当类型为整形时,枚举的工作更接近#define,因为如果未使用,它不占空间

                  (4).              使用inline 替换宏方法,宏方法避免了函数调用的成本
#define MAX(a, b) f( (a) > (b) ? (a) : (b) )
template<typename T>
inline void Max(const T& a, const T& b)
{   tof(a>b?a:b)   }

3           Use const whenever possible

                  (1).              char *p如何用const修饰呢?
当const在星号asterisk左边时修改指针所指向的内容,在asterisk右边时修改指针本身,因此const Widget *pw 与 Widget const * pw 等价

                  (2).              STL iterator的设计是基于指针的,它的const使用也分(1)中的两种情况
const std::vector<int>::iterator iter = .. // like T* const
std::vector<int>::const_iterator cIter = … // const T*

                  (3).              const返回值,通常你会问为什么需要它?首先看表达式:3*4=5非法,既然biuld-in类型不支持该操作,那么用户自定义类型也尽量与biuld-in类型保持一致,重载*操作符,返回const,能够规避该表达式的合法性,同时避免了if(a*b == c) 误打成if(a*b = c)

                  (4).              const修饰成员函数,不允许const成员函数修改对象成员,常用于重载,const对象调用const版本,非const对象调用非const版本

                  (5).              说到const成员函数,不得不讨论它的哲学

1st.    bitwise constness:它要求该成员函数不修改对象的成员变量即可,例如对象中有一指针成员,不修改指针,修改指针指向的值,是符合该哲学的

2nd.  logical constness:它要求成员函数可以修改对象的成员,但从调用的角度看,对象的成员未修改,体现了封装性
实现该哲学,可能需要使用mutable去掉non_static成员的const属性的技术

                  (6).              如何const版本与非const版本的成员函数的业务逻辑基本一致,如果独立实现将带来代码膨胀,为了解决这个问题,可以让一个版本调用另一个版本。const版本调用非const版本这是不允许的,因为它无法保证非const版本不修改对象成员的问题,所以只能让非const版本调用const版本,实现如下:

1st.    将对象转换成const对象,这样对象在调用重载时会选择const版本
static_cast<const Type&>(*this).method();

2nd.  将返回值的const去掉
使用const_static<char>(  static_cast<const Type&>(*this).method();  )

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

                  (1).              区别初始化与赋值,即初始化列表与构造函数
build-in类型的初始化列表与在构造函数赋值的一致的,但为了统一,建议使用前者
常量与引用,无论build-in与否,必须使用初始化列表,即声明时初始化

                  (2).              对象初始化的顺序,基类对象先于派生类对象被初始化,对象内部的成员初始化依照成员声明的顺序进行初始化,而不是依照初始化列表,因此建议初始化列表应保持与成员声明顺序一致

                  (3).              Static objects inside functions are known as local static objects and the other kinds of static objects are known as non-local static objects
Design around the initialization order uncertainty:for instance
one file:
class FileSystem
{
public:
   …   std::size_t numDisks() const;   …
}
extern FileSystem tfs;
another file:
class Directory
{
public:
   Directory( params ):   …
}
Directory::Directory( params )
{
   …   std::size_t disks = tfs.numDisk();   …
}
现在:Directory tempDir( params );
问题是tempDir的初始化需要tfs的初始化,而tfs的初始化与tempDir的初始化顺序是不确定的,因为它们同属non-local static objects
如下代码(单例模式)可以解决这个问题:
class FileSystem{ … };
FileSystem tfs()
{
   static FileSystem fs;
   return fs;
}
class Directory{ … };
Directory::Directory( params )
{
   …   std::size_t disks = tfs().numDisks();   …
}
Directory& tempDir()
{
   static Directory td;
   return td;
}

第二章    Constructors,Destructors,and Assignment Operators
Every class has one or more constructors a destructor and a copy assignment operator

1           Know what functions C++ silently writes and call

                  (1).              If you don’t declare them yourself, compiler will declare a copy constructor, a copy assignment operator, and a destructor. If you declare no constructors at all, compiler will also declare a default constructor for you. All these functions will be both public and inline.
智能的编译器通常是在使用时为你生成

                  (2).              普通类型的类成员时,编译器生成的copy constructor and copy assignment operator may work
如果含有引用成员或者const成员,它们将不能工作,因为它们需要声明即初始化,此后不允许赋值
如果基类的copy constructor and copy assignment operator是private的话,编译器就无法给派生类生成它们,毕竟在构造派生类对象的之前需要构造基类对象

                  (3).              编译器生成的copy constructor and copy assignment operator是浅复制

2           Explicitly disallow the use of compiler generated functions you do no want

                  (1).              Declare the copy constructor and the copy assignment operator private. It will prevent compilers from generating their own version, and keep people from calling it. But the scheme isn’t foolproof, because member and friend functions can still call you private functions. You can just declare them private and don’t define them.
class HomeForSale
{
   private:
     HomeForSale(const HomeForSale&);
     HomeForSale operator=(const HomeForSale&);
}

                  (2).              定义一个不允拷贝的基类,如下:
class Uncopyable
{
protected:
   Uncopyable() {};
   ~Uncopyable() {};
private:
   Uncopyable(const Uncopyable&);
   Uncopyable& operator=(const Uncopyable&);
}
使用:class HomeForSale:private Uncopyable {   …   }

3           Declare destructor virtual in polymorphic base classes

                  (1).              支持多态的基类,假设实现工厂模式,通常工厂模式是基于堆内存的,工厂返回派生类的指针,赋给基类指针
Base *p = getObj();  // getObj()返回基于堆内存的派生类对象指针
delete p; //用基类指针去释放派生类对象内存
如果基类的析构函数是non-virtual的,那么delete p将只析构基类的部分,而不会析构派生类的部分,将析构函数声明成virtual能够解决该问题

                  (2).              If a class not contain virtual functions, that often indicates it is not meant to be used as a base class. When a class is not intended to be a base class, making the destructor virtual is usually a bad idea.
class Point
{
public:
   Point(int xCoord, int yCoord);
   ~Point();
private:
   int x,y;
}
此时一个Point对象在32位机上占64bit,如果将~Point()声明称virtual,Point对象大小将变成96bit,因为virtual需要动态联编,需要一个虚拟函数表,Point保存指向该表的指针

                  (3).              Not all base classes are designed to be used polymorphically. Neither the standard string type for example, not the STL container types are designed to be base classes at all, much less polymorphic ones.
既然这样,(1)中的使用是不允许的,如下则依然允许:
class SpecialString:publicstd::string {   …   };
SpecialString *p = new SpecialString(“Impending Doom”);
delete p;   // 因为析构SpecialString对象并没有使用基类的指针

                  (4).              Unfortunately, C++ offers no derivation-prevention mechanism akin to Java’s final classes or C#’s sealed classes.

                  (5).              即使基类的虚构函数声明为纯虚函数,也必须提供它的定义
class AWOV
{
public:
   virtual ~AWOV()  =0;
}
必须提供:AWOV::~AWOV(){};
这是因为析构的顺序与构造的顺序相反,先析对象的派生类部分,接着继续析构该对象的基类部分。一般不需要在派生类的虚构函数显示调用基类的析构函数,通常编译器自动生成基类析构函数的调用,因此需要给出基类的纯虚析构函数的定义

4           Prevent exceptions from leaving destructor

                  (1).              Destructors should never emit exceptions. If functions called in a destructor may throw, the destructor should catch any exceptions, the swallow them or terminate the program.

1st.    class Widget
{
public:
   … ~Widget(){   …   };
};
void doSomething()
{
   std::vector<Widget>v;
}
此时假设v的数量是10,在v析构的同时,需要析构v容器中的对象,当析构第一Widget对象时发生异常,后面的9个对象将得不到析构

2nd.  因此建议不在析构中throw exception,如果真的必要呢?例如数据库的连接,该怎么办呢?Swallow them or terminate the program
class DBConnection
{
public:
   …
   static DBConnection create();
   void close();
}
class DBConn
{
public:
   …
   ~DBConn();
private:
   DBConnection db;
}
DBConn::~DBConn()
{
   try { db.close(); }
   catch(…){
      make log; 
      std::abort(); //如果没有该语句表示swallow,否则表示中止
   }
}

3rd.   2nd中的接口,未给用户设计反应的接口,可作以下改善
class DBConn
{ //这里的并非inline实现,这样只是为了提高可阅读性
public:
   …
   void close()
   { //用户可使用它手动关闭连接,同时手动处理异常
      db.close();
      closed = true;
   }
   ~DBConn()
   {
      if (!closed)
      {
         try{ db.close();}
         catch(…){make log;}
      }
   }
private:
   DBConnection db;
   bool closed;
}

5           Never call virtual functions during construction or destruction

                  (1).              举例说明,让问题一目了然
class Transaction
{
public:
   Transaction();
   virtual void logTransaction() const =0;
};
Transaction::Transaction()
{
   …  logTransaction();
}
class BuyTransaction: public Transaction
{
public:
   virtual void logTransaction() const;
   …
};
class SellTransaction: public Transaction
{
public:
    virtual void logTransaction() const;
};
此时BuyTransaction b; 构造b之前需要构造基类部分,需要调用logTransaction(),而它是虚函数,需要调用派生类版本,而此时b对象并不存在,发生问题。有时可能会调用基类的logTransaction()版本,但这与我们的初衷也不符,析构函数类似

                  (2).              如果真的需要实现(1)中想要的效果,该如何?
class Transaction
{
public:
   explicit Transaction(const std::string& logInfo);
   void logTransaction(const std::string& logInfo);
   …
};
Transaction::Transaction(const std::string& logInfo)
{
   …   logTransaction(logInfo);
};
class BuyTransaction: public Transaction
{
public:
   BuyTransaction( parameters )
   :Transction(createLogString( parameters) ) //初始化列表调用函数
   {…}
private:
   static std::striing createLogString( parameters );//类成员,独立于对象
};

6           Have assignment operators return a reference to *this

                  (1).              int x, y, z;
x = y = z =15;
既然build-in类型支持链式赋值,那么user-defined类型也应该支持

                  (2).              如何实现呢?让+=,-=,=等返回*this的引用即可

7           Handle assignment to self in operation=

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

1st.    当自我赋值发生,*this与rhs是同一个对象,delete pb将删除this的Bitmap,同时也删除了rhs的Bitmap
解决方案:在delete pb之前加:if (this == &rhs) return *this;

2nd.  operator=成员并非异常安全的成员,new Bitmap(*rhs.pb)将可能发生异常,比如内存空间不足,后者构造函数抛出异常等。而此时delete pb已经执行,pb成了野指针
解决方案:copy and swap
Widget& Widget::operator=(const Widget& rhs)
{
   Bitmap *pOrig = pb;
   pb = new Bitmap(*rhs.pb);
   delete pOrig;  //delete针对地址,而非指针
   return *this;
}

                  (2).              可能有时,有些程序员并不喜欢仅仅是Bitmap的拷贝(估计又合哲学有关),喜欢:
class Widget
{
   void swap(Widget& rhs);
};
Widget& Widget::operator=(const Widget& rhs)
{
   Widget temp(rhs);
   swap(temp);
   return *this;
}
与上同理得格式:
Widget& Widget::operator=(const Widget rhs) //编译器生成的传参拷贝
{
   swap(rhs);
   return *this;
}
Scott Meyers说,编译器实现的拷贝往往比程序员的拷贝代码效率高

                  (3).              为什么在后续改进中去掉了if (this == &rhs) return *this;呢?
因为if语句通常会打断流水线,而且自我赋值通常是很少见的,即使发生了,程序也能够正常处理,没必要为了很少的发生的事去打断流水线,不是吗?

8           Copy all parts of an object

                  (1).              If you take it upon yourself to write copying functions, remember that updating the copying functions while you add a data member to a class.

                  (2).              Any time you take it upon yourself to write copying functions for a derived class, you must take care to also copy the bass class parts.

                  (3).              Don’t try to implement one of copying functions in terms of the other.
不要让拷贝构造函数调用赋值函数,或者赋值函数调用拷贝构造函数,这样的行为错误的,如果为了避免代码冗余,可以让它们调用同一个函数

第三章    Resource Management\

1           Use objects to manage resources

                  (1).              class Investment{   …   };   //工厂模式,返回堆内存的指针
Investment* createInvestment();
void func()
{
   Investment *pInv = createInvestment();
   …
   delete *pInv;
}
如果…中发生了exit调用或者exception等,跳过了delete *pInv,将造成内存泄露,当然小心可能可以避免,当软件进行维护期时,糊涂的程序员在不完全理解的情况下可能会修改…中的程序,怎么办?
解决方案:利用对象的out of scope时自动调用析构函数的特性
          RAII --- Resource Acquisition Is Initialization
void func()
{
   std::auto_ptr<Investment> pInv(createInvestment());
}

                  (2).              当我们设计类似于auto_ptr的资源管理类时,还需要注意拷贝构造函数与赋值函数的问题。即两个对象管理同一资源,在其析构时将释放资源两次。
auto_ptr的方案是:在拷贝前,删除前一资源;
另一种方案是:RCSP --- reference-counting smart pointer,如:std::tr1::shared_ptr

                  (3).              auto_ptr and tr1::shared_ptr是基于delete,而不是delete[]

2           Think carefully about copying behavior in resource-managing classes

                  (1).              Not all resources are heap-based, however, and for such resources, smart pointers like auto_ptr and tr1::shared_ptr are generally inappropriate as resource handlers.
void lock(Mutex *pm);  //例如:互斥量Mutex资源
void unlock(Mutex *pm);
class Lock
{
public:
   explicit Lock(Mutex *pm)
   :mutexPtr(pm);
   {lock( mutexPtr );}
   ~Lock(){ unlock( mutexPtr ); }
private:
   Mutex *mutexPtr;
}
当Mutex m;   Lock ml1(&m);   Lock ml2(ml1); 将出现问题
方案一:禁止拷贝,即declare copy constructor and assignment private
方案二:RCSP --- reference-counting smart pointer,如:std::tr1::shared_ptr

                  (2).              tr1::shared_ptr提供自定义析构处理,替代delete,it may help
class Lock
{
public:
   explicit Lock(Mutex *pm)
   :mutexPtr(pm, unlock);
   {  lock(mutexPtr.get();  }
private:
   std::tr1::shared_ptr<Mutex> mutexPtr;
}
注意:unlock(pm)会被调用
当Lock out of scope时,将调用析构函数,同时析构non-static成员
考虑为什么不直接调用呢?  // 谈设计
lock(pm);
std::tr1::shared_ptr<Mutex> mutexPtr(pm, unlock);
提示:resources are acquired during construction and released during destruction

3           Provide access to raw resources in resource managing classes

                  (1).              通常一些API需要访问资源管理类所管理的资源
auto_ptr与tr1::shared_ptr都有get成员,返回所管理的资源
通常资源管理类只管理资源的指针,类似于auto_ptr与tr1::shared_ptr,需要重载->和.操作符

                  (2).              FontHandle getFont();  //字体句柄
void releaseFont(FontHandle fh);
class Font     //FontHandle管理类
{
public:
   explicit Font(FontHandle fh)
   : f(fh)
   {}
   ~Font(){   releaseFont(f);   }
   FontHandle get() const {return f};  //添加get显示返回FontHandle资源
   operator FontHandle() const {return f;}  //隐式类型转换,是重载操作符
private:
   FontHandle f;
}
In general, explicit conversion is safer, but implicit conversion is more convenient.
书中举例说明implicit conversion是如何危险:
Font f1(getFont());
FontHandle f2 = f1; //oops! Meant to copy a Font object, but instead implicitly
                  converted f1 into its underlying FontHandle, then copy that
当f1析构时,f2成了野孩子了,如何此时操作f2将可能给程序带来灾难
注意:为什么get不会有这个问题呢?其实作者的意思是如果get发生了类似的事情,通常程序是知道的,而这里的问题,是程序员无意识下造成的

4           Use the same form in corresponding uses of new and delete.

                  (1).              new match delete, new[] match delete[], 否则行为未定义

                  (2).              When you employ a new expression, two things happen. First, memory is allocated. Second, one or more constructors are called for that memory.
When you employ a delete expression, two other things happen: one or more destructors are called for the memory, then the memory is deallocated.

                  (3).              为什么析构new[]时需要delete[],而delete不行呢?
通常是因为内存的布局不一样,即new[]创建对象还保存了对象的个数,delete[]会去读取对象的个数,才能知道调用多少次析构函数,而delete不会

                  (4).              int等biuld-in类型,虽然没有析构函数,new与delete的行为同上

                  (5).              顺便学习下:typedef std::string AddressLines[4];
定义了AddressLines类型,该类型是std::string[4];
同理需要delete[]去回收new AddressLines

5           Store newed objects in smart pointers in standalone statements.

                  (1).              老规矩:举例来引出主题
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);

1st.    调用:processWidget(new Widget, priority());
它不能编译。std::tr1::shared_ptr<Widget>与Widget *之间不存在隐式转换

2nd.  调用:processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());
上述调用将发生3件事情:
Call priority.
Execute “new Widget”
Call the tr1::shared_ptr constructor.
在Java或C#中,三者调用的顺序是固定的,而C++不固定,如果发生如下执行顺序:“new Widget” -> Call priority -> Call tr1::shared_ptr constructor
,且此时priority发生异常,tr1::shared_ptr constructor将不会发生,这样new Widget资源将泄露
解决方案:std::tr1::shared_ptr<Widget>pw(new Widget)
          processWidget(pw, priority());
即使用拆开的语句避免该情况的发生

第四章    Designs and Declarations

1           Make interfaces easy to use correctly and hard to use incorrectly.

                  (1).              完美接口的定义:Ideally, if an attempted use of an interface won’t do what the client expects, the code won’t compile; and if the code does compile, it will do what the client wants.

                  (2).              class Date
{
public:
   Date(int month, int day, int year);
}
这个接口似乎很完美,但有两种情况很容易用错,参数顺序与参数打字失误
参数打字失误例如:2005-3-30,Date d(2, 20, 2005),,将3打成2
解决方案:
struct Day            struct Month            struct Year
{                   {                     {
  explicit Day(int d);     explicit Month(int m);    explicit Year(int y);
  :val(d) {}             :val(m){}              :val(y){}
  int val;                int val;                int val;
}                   }                     }
class Date
{
public:
   Date(const Month& m, const Day& d, const Year& y);
};
为了让Month参数更有意义,可以定义些枚举常量,但枚举常量并不是很安全,因为它可以被相应的整数替代,做如下改善:
class Month
{
public:
   static Month Jan() {return Month(1) ; }
   …
   static Month Dec() {return Month(12); }
private:
   explicit Month(int m);  //禁止外部直接构造Month对象,只能通过静态方
                        法获得Month对象
}
作者的这个似乎显得不够充分,但它解决相应问题的方案是值得借鉴的

                  (3).              使用资源管理类来管理资源,像tr1::shared_ptr,也是接口设计的好案例
cross-DLL problem:this problem crops up when an object is create using new in one dynamically linked library (DLL)but is deleted in a different DLL. On many platforms, such cross-DLL new/delete pairs lead to runtime error. tr1::shared_ptr avoids the problem, because its default deleter uses delete from the same DLL where the tr1::shared_ptr is created.

                  (4).              尽量保持一致的风格
例如:3 * 4 = 5; 在bulid-in 类型中是错误,那么user defined类型也要拒绝该操作,通过重载operator * 返回const来实现
例如:大部分STL都支持size接口,它返回容器内部元素的个数,那么你定义的接口就不要变成count

2           Treat class design as type design

                  (1).              是否使用new分配资源?

                  (2).              Constructor和Assignment operator如何实现,有何不同?

                  (3).              pass by value or pass by reference ?

                  (4).              成员取值限制?

                  (5).              是否参与继承?

                  (6).              支持哪些操作符?

                  (7).              是否需要禁止一些接口,声明为private

                  (8).              成员的public protected private属性,考虑友元

                  (9).              算法性能 / 异常安全 / 多线程 等是否支持

              (10).              是否声明成模板更好呢?如果声明一个类型系列的话

3           Prefer pass-by-reference-to-const to pass-by-value.

                  (1).              class Person
{
public:
   Person();
   virtual ~Person();
private:
   std::string name;
   std::string address;
}
class Student: public Person
{
public:
   Student();
   ~Student();
private:
   std:string schoolName;
   std:string schoolAddress;
}
bool validateStudent(Student s);
调用Student stu; -> bool ok = validateStudent(stu);
构造Student -> 构造Person -> 构造4个string,函数返回时还要析构它们
即传值成本是6个constuctor and 6 个 destructor,如果传引用,几乎0成本

                  (2).              引用与指针具有多态特性,而传值没有,即有slicing problem切割问题
class WindowWithScrollBars: public Window;
且Window类有一个virtual成员display
doSomething(Window w){  w.display(); }
调用WindowWithScrollBars  wwsb; -> doSomething( wwsb );
过程创建Window临时对象,拷贝wwsb与临时对象对应的成员,此时该对象是纯洁的Window对象,而非基类指针指向派生类对象的情况,固无多态

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

                  (1).              还是例证,有理数类Rational numbers
class Rational
{
public:
   Ration(int numerator = 0, int denominator = 1);  //翻译:分子与分母
private:
   int n,d;
friend const Rational operator*(const Rational& lhs, const Rational& rhs);
};
讨论operator*的实现,包括返回值是否引用的讨论

                  (2).              const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
   Rational result(lhs.n*rhs.n, lhs.d*rhs.d);
   return result;
}
问题:返回局部变量的引用或指针

                  (3).              const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
   Rational *result = new Rational(lhs.n*rhs.n, lhs.d*rhs.d);
   return *result;
}
问题:谁负责delete呢?即使细心的程序员负责
假如:Rational result, x, y, z;
      result = x*y*z;  //粗心程序员做的,也不能怪他,接口本来就没设计好
调用了两个operator*,也就调用了两个new,使用delete result,回收一个资源,另一个资源呢?

                  (4).              const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
   static Ration result;
   …
   return result;
}
乍眼一看,nice,只需构造与析构一次
问题:Rational a,b,c,d;
      if((a*b) == (c*d))   判断永为真

                  (5).              最终方案:
inline const Rational operator*(const Rational& lhs, const Rational& rhs)
{
   return Rational(lhs.n*rhs.n, lhs.d*rhs.d);
}
inline是解决了函数调用的成本,但似乎与避免Rational的构造与析构无关?作者指出,在return语句构造返回的,大部分的编译器往往会给与优化

5           Declare data members private

                  (1).              通常将成员声明为private,然后通过public的getter与setter访问,体现了封装性,称这为细粒度访问(fine-grained acess)

                  (2).              Public means unencapsulated, and practically speaking, unencapsulated means unchangeable, especially for classes that are widely used.
更好的封装性带来更大的实现灵活性

                  (3).              Protected data members are thus as unencapsulated as public ones.
From an encapsulation point view, there are really only two access levels: private and everything else.

6           Prefer non-member non-friend functions to member functions.

                  (1).              class WebBrowser
{
public:
   void clearCache():
   void clearHistory();
   void removeCookie();
};
void clearEverything(): //revoke clearCache clearHistory oveCookie
讨论:将clearEverything作为类WebBrowse好呢?还是非成员函数好呢?

                  (2).              If something is encapsulated, it’s hidden from view. The more something is encapsulated, the fewer things can see it. The fewer things can see it, the greater flexibility we have to change it, because our changes directly affect only those things that can see what we change.
即更好的封装性带来更大的实现灵活性

1st.    如果clearEverything()实现为类成员,此时需要修改clearCache()成员,那同时需要改变clearEverything(),(这里似乎不需要,假设需要),这就限制了修改实现的灵活性。而如果实现为非类成员,类似于Browser相关的工具函数,此时修改clearCache()成员,而clearEverything()的修改就不关我们的事了,因为它不是类设计必需的,而是方便接口

2nd.  如果clearEverything()实现为类成员,那么它就能随意访问private成员,而实现为non-member且non-friend,则限制了private成员的访问,提高了封装性

3rd.   non-friend与类成员实现类似,它可以访问private成员

                  (3).              当将clearEverything()实现为non-member and non-friend时,可以将这些函数整理成Browser的方便工具集,置于namespace WebBrowserStuff中,然后将Browser的工具分类,分别列在不同的头文件中
然后根据需要包含相关的工具,例如Cookie相关工具等

                  (4).              Std库就是很大的一个名称空间,里面有<vector> <algorithm> <memory>头文件,这些都是经过分类的工具集,这样实现了编译独立性

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

                  (1).              class Rational
{
public:
   Rational(int numerator = 0, int denominator = 1);
   const Rational operator*(const Rational& rhs) const; //成员实现
} ;
讨论operator实现成成员好呢?还是非成员?
成员实现的问题:
Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Ration result = oneEighth * oneHalf;  //fine
result = oneHalf * 2; //fine  <1>
result = 2 * oneHalf; //error <2>

                  (2).              <1>为什么fine呢?
result = oneHalf.operator*(2);
由于构造函数没有声明explicit所以存在2到Rational的隐式转换

                  (3).              <2>为什么不行呢?
result = 2.operator*(oneHalf); //int可能没有重载Rational版本的*操作符
搜索过成员版本,当然还会继续搜索全部版本
result = operator*(2, oneHalf); // 很遗憾,全局中没有该版本

                  (4).              解决方案:非成员
const Rational operator*(const Rational& lhs, const Ration& rhs)
{
   return Rational(….); //分子*分子,分母*分母
}
调用<1>,<2>都fine,nice
考虑是否需要friend呢?不需要,用公共版本可以实现,为什么需要友元呢?
友元是要访问类私有成员时才需要的

                  (5).              Whenever you can avoid friend functions, you should, because, much as in real life, friends are often more trouble than they’re worth.

                  (6).              最后注意:该项是在C++的Object-Oriented领域,而不是在C++的Template领域,see Item 1(第一章1)

8           Consider support for a non-throwing swap.

                  (1).              STL中提供了swap()算法,实现如下:
namespace std {
   template<typename T>
   void swap(T& a, T&b)
   {
      T temp(a);
      a = b;
      b = temp;
   }
}
为了让你设计的类能使用该算法,你需要提供copy constructor and copy assignment operator

                  (2).              (1)中的swap调用了3次copy,一次copy constructor, 两次assignment operator,如何避免这种拷贝呢?常见的是”pimpl idiom”(pointer to implementation)
class WidgetImpl{}; //含许多成员,且含copy constructor and assignment operator
class Widget
{
public:
   Widget(const Widget& rhs);
   Widget& operator=(const Widget& rhs);
   {
       *pImpl = *(rhs.pImpl);  //内容拷贝
   }
private:
   WidgetImpl *pImpl;
}
如上,Widget只想交换pImpl指针,STL的通用版本将帮不了忙,what to do?

                  (3).              解决方案:用STL的swap模板为Widget类型提供具体化
namespace std {
   template<>
   void swap<Widget>(Widget& a, Widget& b)
   {
      swap(a.pImpl, b.pImpl);
   }
}
问题:pImpl是类Widget的私有成员,以上代码不能编译,what to do?

                  (4).              解决方案:访问私有变量,无非两种常见方法,friend与成员,定义个Widget的swap成员,然后STL的swap的Widget类型的具体化中调用Widget类的swap成员,代码实现:
class Widget
{
public:
   void swap(Widget& other)
   {
      using std::swap; 
      swap(pImpl, other.pImpl); //这里使用的是STL通用模板,pImpl非Widget
   }
};
namespace std {
   template<>
   void swap<Widget>(Widget& a, Widget& b)
   {
      a.swap(b);
   }
}

                  (5).              如果Widget与WidgetImpl是类,(4)是完全可以工作的
现假设Widget与WidgetImpl是模板,而非类,如何实现呢?类似思想:
template<typename T>
class WidgetImpl{ …… };
template<typename T>
class Widget{ … };
namespace std{
   template<typename T>
   template<typename T>
   void swap<Widget<T> >(Widget<T>& a, Widget<T>& b)
   {
      a.swap(b);
   }
}
问题:We’re trying to partially specialize a function template(std::swap), but though C++ allows partial specialization of class templates, it doesn’t allow it for function templates.
首先什么叫局部具体化(partially specialize),但从这个例子看,我也有点蒙,看个其它例子:
template<typename X, typename Y>
void f(X a, Y b){}
template<typname X>
void f<X, char>(X a, char b){};
这里就是说X,char组合比X,Y组合更具体,但还是模板,前面的例子也一样,作者的意思也是Widget<T> 要比T更具体,但还是模板
既然函数模板中不允许局部具体化,what to do?

                  (6).              解决方案:局部具体化被拒绝了,那试试模板重载
namespace std {
   template<typename T>
   void swap(Widget<T>& a, Widget<T>& b)
   {  a.swap(b);  }
}
注意:与局部具体化在格式上的区别是swap<Widget<T> >
问题:In general, overloading function templates is fine, but std is a special namescape, and the rules governing it are special, too. It’s okay to totally specialize templates in std, but it’s not okay to add new templates(or classes or functions or anything else) to std. The contends of std are determined solely by the C++ standardization committee. What to do?

                  (7).              解决方案:既然std不允我们扩展,那就不用std名称空间了
namespace WidgetStuff {
   template<typename T>
   class Widget { … };
   template<typename T>
   void swap(Widget<T>& a, Widget<T>& b)  //non-member
   {
      a.swap(b);
   }
}

                  (8).              (7)终于解决了应用于模板的问题了,swap终于出炉了,现在考虑下如何调用
template<typname T>
void doSomething(T& obj1, T&obj2)
{
   swap(obj1, obj2);
}
请问swap调用的std空间的通用版本,还是std空间具体化的空间(可能存在),还是WidgetSutff空间的版本呢(可能存在)?
如果想:WidgeStuff版本存在就调用它,不存在就调用std版本的?

                  (9).              解决方案:使用using
void doSomething(T& obj1, T&obj2)
{
   using std::swap;
   swap(obj1, obj2);
}
该例的swap调用,首先查找全局空间的swap或者与T类型声明时同一个空间,然后查找std空间
切勿使用std::swap(obj1, obj2);这样只会查找std空间

              (10).              附注:If you want to have you class-specializing version of swap classed in as many contexts as possible (and you do), you need to write both a non-member version in the same namespace as your class and a specialization of std::swap.
我不是理解作者的意思:可能是说,如果你在提供了non-member版本后,最好再提供std::swap版本,这样,(8) 和 (9) 两种环境(context)下调用,都将成功,给接口使用者--用户,最大的方便

第五章    Implementations
实现往往需要在多种情况下进行折中:异常,code bloat,耦合coupling等

1           Postpone variable definitions as long as possible.

                  (1).              例如:有些变量在使用之前,函数已经退出,或者发生异常,而未使用,带来了不必要的构造与析构成本

                  (2).              Widget w;                   Widget w;
for (int i=0; i<n; i++)          for (int i=0; i<n; i++)
{                          {
   w=some value;               Widget w(some value);
} // Approach:A             } // Approach:B
使用A呢还是使用B呢?
A成本:1constuctor + 1destuctor + n assignment
B成本:n constructor + n destructor
明显当n>2时,A成本更低

2           Minimize casting.

                  (1).              If you’re coming to C++ from C, Java, or C#, take note, because casting in those languages is more necessary and less dangerous than in C++.

                  (2).              回忆下类型转换的格式:

1st.    (T) expression

2nd.  T(expression)

3rd.   const_cast<T>(expression)
dynamic_cast<T>(expression)  //效率很低,需要调用字符串的比较
reinterpret_cast<T>(expression)
static_cast<T>(expression)

                  (3).              这里准备描述一个非常重要的特点,相对比较抽象,我也有点蒙
class Base{…};
class Derived: public Base{…};
Derive d;
Base *pd = &d;
Sometimes the two pointer values will not be the same. (指pd与&d)
程序需要在运行时,根据指针的类型,判断后进行操作.
注意:以上仅仅发生在Object-Oriented C++ (consult Item 1 第一章1)
作者指出这和对象在C++中布局有关,这种布局可能随编译器不同而不同,且When multiple inheritance is in use, it happens virtually all the time, but it can happen under single inheritance, too.
注意:这种情况不会发生在C,C#,Java中,但会发生在C++中
      下面的例子将更具体得解释

                  (4).              class Window
{
public:
   virtual void onResize(){…}
};
class SpecialWindow: public Window
{
public:
   virtual void onResize()
   {
      static_cast<Window>(*this).onResize(); //调用它之前,需要调用基类版本
      ………
   }
}
说明:派生类对象的onResize()调用之前,需要调用基类的onResize();
问题:当static_cast<Window>(*this)调用时,事情并非你想象的,它只是创建
      临时对象拷贝了*this对象的基类部分,此时再调用onResize(),仅仅是
      针对这个临时对象,并不对*this对象本身生效,即无法实现调用*this
      对象的基类版本onSize();
解决方案:尽量避免使用cast
class SpecialWindow: public Window
{
public:
   virtual void onResize()
   {
      Window::onResize(); //调用它之前,需要*this调用基类版本
   }
}

                  (5).              为什么dynamic_cast严重影响性能呢?在性能很关键的场合少用
主要是因为它需要对类名进行比较,而且是逐级比较,当在继承体系中的继承级别较深或者多继承中,该性能影响将更明显
dynamic_cast通常可以通过virtual来避免,在某些无法避免的场合时,也要保证避免级联cascading dynamic_cast,级联如下说明:
if (SpecialWindow1 *psw1 = dynamic_cast< SpecialWindow1>(pObj) {…}
else if (SpecialWindow2 *psw2 = dynamic_cast< SpecialWindow2>(pObj) {…}
else if (SpecialWindow3 *psw3 = dynamic_cast< SpecialWindow3>(pObj) {…}
…….. 
通常使用virtual成员解决

3           Avoid returning “handles” to object internals.

                  (1).              Handle的概念,以前只知道一个handle对应一个资源,完了,书中给了一个简单的解释我很喜欢:References, pointers and iterators are all handles (ways to get at other objects).

                  (2).              class Point
{
public:
   Point(int x, int y);
private:
   int x;
   int y;
};
class RectData
{
  Point ulhc;
  Point lrhc;   //默认private属性
};
class Rectangle
{
private:
   std::tr1::shared_ptr<RectData> pData;
};
假设需要为Rectangle提供upperLeft与lowerRight成员
Point& upperLeft() const {return pData->ulhc;}
Point& lowerRight() const {return pData->lrhc;}
问题:pData是私有成员,返回引用,即handle句柄,使用户可以修改pData,
      与const成员声明矛盾以及破坏了封装性

                  (3).              解决方法:返回const
const Point& upperLeft() const {return pData->ulhc;}
const Point& lowerRight() const {return pData->lrhc;}
OK,这个方案很完美了,但作者还是建议尽量避免让成员返回类内部成员的句柄,这种有时会造成danliing handles,它的定义是:handles that refer to part of objects that don’t exist any longer.
class GUIObject{…};
const Rectangle boundingBox(const GUIObject& obj);
GUIObject *pgo;
const Point *pUpperLeft = &(boundingBox(*pgo).upperLeft());
问题:波undingBox(*pgo)返回临时对象,在该语句结束后,临时对象的生命
      也结束了,此时保留它的引用有意义吗?

                  (4).              作者并非说不该返回类对象内部成员的句柄,有时你不得不,例如:operator[]允许引用strings和vectors的生成,operator[]s就是返回引用,且非const

4           Strive for exception-safe code.

                  (1).              什么叫异常安全函数:

1st.    Leak no resources.

2nd.  Don’t allow data structures to become corrupted.

                  (2).              Exception-safe functions offer one of three guarantees:
提供下述三种保证之一的函数方可称为异常安全的函数

1st.    Functions offering the basic guarantee promise that if exception is thrown, everything in the program remains in a valid state.

2nd.  Functions offering the strong guarantee promise that if an exception is thrown, the state of the program is unchanged.

3rd.   Functions offering the nothrow guarantee promise never to throw exceptions

                  (3).              int doSomething() throw()
该函数并非不抛出异常,只是如果抛出异常,将是一种严重的错误,此时unexpected函数将被调用

                  (4).              class PrettyMenu
{
public:
   void changeBackgound(std::istream& imgSrc);
private:
   Mutex mutex;
   Image *bgImage;
   int imageChanges;
};
void PrettyMenu::changeBackgound(std::istream& imcSrc)
{
   lock(&mutex);
   delete bgImage;
   ++imageChanges;
   bgImage = new Image(imgSrc);
   unlock(&mutex);
}
问题:当new抛出异常,mutex未调用unlock,造成mutex无法使用,即资源
      泄露,且bgImage指针被损坏

                  (5).              方案:资源管理类,像tr1::shared_ptr等,前面介绍的Lock(Item 14)可以帮
      忙
class PrettyMenu
{
   std::tr1::shared_ptr<Image> bgImage;
}
void PrettyMenu::changeBackgound(std::istream& imcSrc)
{
   Lock m1(&mutex);
   bgImage.reset(new Image(imgSrc));
   ++imageChanges;
}
问题:上诉代码已经能够解决(4)中的问题了,美中不足的是,它提供的仅
      仅是the basic exception safety guarantee
解释:(我也有点蒙)new Image(imgSrc)语句发生了两件事,一是分配内存,
      二是构造Image,当构造Image时,抛出异常,会改变输入流的位置,
      (输入流是指imgSrc吗?如果是,作者随后给出的并未解决该问题,
      难道是bgImage?首先它算input stream吗?再说好像reset未被调用的)
      于是仅仅满足保证一:异常发生后程序状态仍合法,而非无改变

                  (6).              解决方案:只需要交换指定的部分,mutex并不需要交互,方法是”pimpl idiom”
         指向实现的指针,保证交换前后状态不变的方法是:copy and swap
struct PMImpl
{
   std::tr1::shared_ptr<Image> bgImage;
   int imageChanges;
};
class PrettyMenu
{
private:
   Mutex mutex;
   std::tr1::shared_ptr<PMImpl> pImpl;
};
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
   using std::swap;
   Lock m1(&mutex);
   std::tr1::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl);
   pNew->bgImage.reset(new Image(imgSrc)); //仍改变了imgSrc的输入流位置
   ++pNew->imageChanges;
   swap(pImpl, pNew);  //交换的指针,成本低,” pimpl idiom”的优点
};

                  (7).              软件只有异常安全和异常不安全之分,没有局部异常安全的说法,所以当你的软件中调用了一个异常不安全的函数时,你的软件就属于异常不安全的

                  (8).              void doSomething()
{
  … 
  f1();
  f2();
  …
}
问题:如果函数f1只提供承诺一,基本保证,那么doSomething如何提供承
      诺二,健壮承诺?
解决方案:通常在调用f1之前,保存环境,当f1抛出异常了,就进行恢复
          但是如果f1修改的是全局资源,像提交了数据库,恢复是很难的
问题:即使f1与f2都提供了承诺2,doSomething也很难提供承诺2,假设f1
      执行成功,执行f2抛出异常,此时只能返回到f2调用前的状态,并不
      能返回到doSomething调用前的状态
说明:A function can usually offer a guarantee no stronger than the weakest
      guarantee the functions it call.

5           Understand the ins and outs of inlining

                  (1).              inline很明显的优势是,无须函数调用成本,缺点是可能代码膨胀,而代码膨胀,就带来跨页访问,就带来cache hit rate降低,作者意指inline很大的函数

                  (2).              inline的两种方式,隐式和显式,隐式就是类声明时即定义

                  (3).              简单短小的inline不仅避免函数调用的成本的,而且往往可能生成的代码并不膨胀,反而更小,例如:
int age() const {return theAge;}(在类声明中)  //往往背后是编译器的优化

                  (4).              Inlining in most C++ programs is a compile-time activity.
编译生成内联代码,在有些环境中,可能是在link阶段,有些发生在运行阶段,像.NET Common Language Infrastructure (CLI)就发生在运行阶段,而C++是在编译阶段,于是,内联函数往往在头文件中提供

                  (5).              注意:不能使用内联函数的函数指针,毕竟它不是函数,只在编译器阶段进行了函数应有的检查,像类型检查

                  (6).              请不要将构造函数或析构函数内联,因为它们做了很多的工作
class Derived: public Base
{
public:
   Derived() {};
}

                  (7).              最后内联函数,是不支持调试的

6           Minimize compilation dependencies between files.

                  (1).              class Person
{
public:
   Person(const std::string& name, const Data& birthday, const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
private:
   std::string theName;
   Date theBirthDate;
   Address theAddress;
};
上述代码不能通过编译,因为未给出string,Date和Address的声明,需要添加:#include<string>,#include “date.h”,#include “address.h”
问题:当date.h,address.h或者string.h文件发生任何改变,Person类就需要
      重编译,Person类的客户也需要重编译
解决方案:不改变date.h,address.h,string.h,通常的做法是接口与实现分离

                  (2).              namespace std
{
   class string;
}
class Date;
class Address;
class Person { …同(1)…}
说明:Person类的定义只需要知道Date,Adress,string是什么,前向声明是
      不错的选择,但是如此使用,往往使问题更复杂
问题一:string是basic_string<char>的typedef,因此前向声明是不正确的
问题二:Person p( params ); 当实例化时,无法得知,需要为Person类分配多
        少内存,而在Smalltalk和Java中不存在该问题,因为它隐藏的实现
        是Person *p;

                  (3).              解决方案:为了使Person p能够有Person *p的效果,很容易想到”pimpl idiom”
         (pointer to implementation)
#include <string>  //standard library components shouldn’t be c
#include <memory> //for tr1::shared_ptr
class PersonImpl;  // forward-declared
class Date;
class Address;
class Person
{
public:
   //同(1)
private:
   std::tr1::shared_ptr<PersonImpl> pImpl;
};
说明:这样一来,只依赖Date,PersonImpl,Address类的声明,而不依赖它
      们的定义,所以当它们的定义改变,Person类无需重编译,既然Person
      无需重编译,那Person的客户也需重编译。因此你可以任意修改Person
      的实现,即PersonImpl     
总结:Avoid using objects when object references and pointer will do
      Depend on class declarations instead of class definitions whenever you can.
      Provide separate heater files for declarations and definitions说明:在项目
      我们常常将声明放在头文件中,将定义放在源文件中,这样一来,我们
      就可以省去上面的前向声明,取而代之的是直接包含头文件

                  (4).              Classes like Person that employ the pimpl idiom are often called Handle classes.

                  (5).              上面已经讲完了Person的头文件,即声明文件,那Person的源文件,即定义文件就简单多了:
#include “Person.h”
#include “PersonImpl.h” //including Date,Address
Person::Person(const std::string& name, const Date& birthday, const Address& addr):pImpl(new PersonImpl(name, birthday, addr) {}
std::string Person::name() const
{
   return pIpml->name();
}

                  (6).              Java与.NET中有接口的概念,C++没有,但有与其类似的接口类,C++的接口类更加自由。接口类可以同”pimpl idiom”一样,发挥上述效果
class Person
{
public:
   virtual ~Person();
   virtual std::string name() const = 0;
   virtual std::string birthDate() const = 0;
   virtual std::string address() const = 0;
};
如此一来,用户使用Person,只能使用引用或指针,因为接口类含纯虚方法,不能实例化,即Person p是不合法的
问题:不能使用Person p,那如何创建Person对象呢?

                  (7).              解决方案:常见的是类成员的工厂模式,返回动态内存空间的对象
class Person
{
public:
   static std:tr1::shared_ptr<Person>
      create(const std::string& name, const Date& birthday, const Address& addr);
};
create的通常是重载的实现,根据不同的参数创建,不同的派生类对象

                  (8).              “pimpl idoim”和interface class也有缺点,由于封装,函数调用的成本增大,接口类的virtual需要虚拟函数表的指针,且它们不能更大的利用inline?(蒙)
解释:不能inline是因为,inline函数需要放置头文件中,而现在接口与实现
      分离,那些实现中的短小函数,就不能够使用inline了(还是蒙)

                  (9).              世界上没有免费的午餐,它们同样有缺点?如何在它们的优点与缺点中进行抉择呢?
分析缺点,无非就是,速度(函数调用成本)与空间(virtual虚拟函数表的指针),所以只有在速度与空间非常重要时,才放弃“pimpl idoim”和interface class

第六章    Inheritance and Object-Oriented Design

1           Make sure public inheritance models “is a”

                  (1).              C++中面向对象编程最重要的规则是:public inheritance means “is a”
即Everything that applies to base classes must also apply to derived classes, because every derive class object is a base class object.

                  (2).              人与学生:学生是人,人不一定是学生,可以使用public继承

                  (3).              鸟与企鹅:企鹅是一种鸟,鸟不一定是企鹅,而鸟会飞,而企鹅不会飞,这在C++中如何实现呢?明明是”is a”,却不能用public继承,为什么呢?这不是C++的错,而是语言(作者用英语)描述的不准确,”鸟会飞”,这句话本来就不准确,并非所有的鸟都能飞,于是C++不能用public继承实现是可以理解的
问题:如果想用public继承呢?
解决方案一:将鸟类再细分为会飞的鸟与不会飞的鸟
class Bird{…};  // no fly() 抽象
class FlyingBird:public Bird  // 一种具体的,即具有飞行能力
{public: virtual void fly();}
class Penguin:public Bird{…};
解决方案二:当让penguin飞的时候,就提示错误(不佳)
void error(const std::string& msg);
class Penguin: public Bird
{
public:
   virtual void fly(){ error(“Cann’t fly!”);}
};
这种方案不好,待到程序运行时,才发现企鹅不能飞,上述提过,避免设计用户容易使用错的接口|
解决方案三:有些应用,并不关心鸟儿是否能飞,那么就完全没有必要区别能飞与不能飞,即FlyingBird的实现是多余的
class Bird{…}; // no fly
class Penguin: public Bird{…};  // no fly
调用Penguin p; p.fly();编译器就会报错

                  (4).              长方形与正方形的关系,可以用public继承吗?
正方形是一种长方形,好像能?可是public继承要求,应用于基类对象的属性必然能应用于其子类对象。那么长方形长宽可以不一样长,正方形能吗?所以不能使用public继承

2           Avoid hiding inherited names

                  (1).              继承中相关的隐藏问题,其实属于作用域的范畴(经典)

                  (2).              int x;
void someFunc()
{
   double x;
   std::cin >> x;
};
在someFunc的作用域中,double x隐藏了全局的int x,隐藏仅仅与名称有关,与名称对应的类型并无关系

                  (3).              基类与派生类的隐藏问题,其实可看作是基类与派生类作用域的问题
classs Base
{
public:
   virtual void mf1() = 0; // pure
   virtual void mf1(int);
   virtual void mf2();
   void mf3();
   void mf3(double);
private:
   int x;
};
class Derived: public Base
{
public:
   virtual void mf1();
   void mf3();
   void mf4();
};
假设mf4的实现:
void Derived::mf4()
{
   mf2(); //调用基类的mf2()
}
编译器的就mf2名称的搜索过程:先在mf4()函数内的作用域搜索mf2的名称,找不到,然后在派生类的空间中搜索,只有mf1与mf4,没有mf2,再然后在基类空间中搜索,发现mf2,所以调用的是基类的mf2。如果基类空间也没有,接下来一次搜索名称空间,最后是全局空间
假设Derived的客户如下使用:
Derived d;
int x;
d.mf1();  // fine, calls Derived::mf1
d.mf1(x); // error, Derived::mf1 hides Base::mf1
d.mf3(); // fine, calls Derived::mf3
d.mf3(x); //Derived::mf3 hides Base::mf3
解释:为什么Derived::mf1会隐藏Base::mf1呢?它们不是重载吗?我们可以简单的理解成,重载就是类型不同,又因为隐藏仅仅与名称有关,与名称对应的类型并无关系。因此搜索mf1名称时,在派生类空间中,就搜到了mf1,但类型不符,于是编译器就报错了。mf3也是同理,virtual =0 与virtual仅仅也可理解成类型的不同
问题:前一Item已经说明public继承意味着”is a”的关系,即基类的重载版本,
     也应该可应用于派生类
解决方案:If you inherit from a base class with overload functions and you want to
          redefine or override only some of them, you need to include a using
          declaration for each name you’d otherwise be hiding.
class Derived: public Base
{
public:
   using Base::mf1;
   using Base::mf3;
   virtual void mf1();
   void mf3();
   void mf4();
};
思考:此时Base::mf3()与Derived::mf3()会冲突吗?而extern x 与int x会冲突。

                  (4).              以上都是public继承,那么private继承又会放生什么呢?
需要理解private继承与public继承最大的区别,在于private继承,父类的接口属性全部变成了private,这种private属性只是从客户调用的角度看的,而对于Derived的成员函数实现来看,没有任何区别。所以基类与派生类的作用域空间的概念同public

3           Differentiate between inheritance of interface and inheritance of implementation

                  (1).              继承接口与继承实现:non-virtual,impure virtual,pure virtual
class Shape
{
public:
   virtual void draw() const = 0;
   virtual void error(const std::string& msg);
   int objectID() const;
};
class Rectangle: public Shape { … };

                  (2).              pure virtual(draw)只继承了接口,派生类需要自己去实现
当然pure virtual在C++中是可以实现的,只是访问时需要使用作用域操作符
void Shape::draw(){……..}
Shape *ps = new Shape; //error,即使pure virtual draw有实现
Shape *ps1 = new Rectangle;
ps1->draw(); // calls Rectangle::draw
ps1->Shape::draw(); // calls Base::draw

                  (3).              impure virtual(error)继承了接口的同时,又继承了默认的实现
问题:用户的派生类,如果没有提供error的实现,就会使用基类默认的实现。
      而当用户的派生类,需要定义不同的error的实现,然后不小忘记了,
      在这种情况下,编译器将不会有任何提示下去使用基类的默认实现,这
      不是我们希望的,我们所希望的是用户有意识得使用基类的默认实现
解决方案:将error声明为pure virtual,然后声明个protected属性的defaultErr
          成员,如果用户的派生类想使用默认实现,就在派生类的error的
          实现中调用基类的defaultErr,如果忘记实现error,编译器会提示,
          因为error是pure virtual
问题:有些人说这种解决方案还是有缺点,它浪费了名称空间的名称,即一个
      error却多是用了defaultErr名称
解决方案:直接提供实现error,然后派生类实现error接口时,调用继类error,
         唯一与上不同的是,此时error实现是public属性

                  (4).              non-virtual(objectID)继承接口的同时,又继承了必须服从的实现(不可改写)

                  (5).              菜鸟设计类时常见的错误

1st.    Declare all functions non-virtual
这么做要么因为该类不做基类使用,要么因为virtual影响性能。
但从经验上讲80-20规则,80%的时间运行着20%的那部分代码,也许virtual出现在另外80%的代码中,这对性能几乎没有影响,所以你只需要关注那20%的代码,毕竟只有部分的成员是virtual

2nd.  Declare all member function virtual
如果所有的类都是虚拟的,很少用户会继承一个类时,去改写所有实现

4           Consider alternatives to virtual functions
通常程序员在设计一个继承体系中,往往首先想到virtual与多态,在这里将提出几个常见的替代品

                  (1).              class GameCharacter
{
public:
   virtual int healthValue() const;   //返回角色的生命值
};
非pure virtual说明提供了默认计算方法
派生类改写healthValue()方法,来实现不同角色的不同计算公式

                  (2).              The Template Method Pattern via the Non-Virtual Interface Idiom
class GameCharacter
{
public:
   int healthValue() const
   {
      int retVal = doHealthValue();
      return retVal;
   }
private:
   virtual int doHealthValue() const {…}
};
healthValue提供了non-virtual interface (NVI),派生类通过改写doHealthValue()来实现不同的生命值计算公式
这种方法的优势是,healthValue在调用doHealthValue()之前,之后可以做一些统一的处理,例如:判断适当的上下文,访问互斥量等。即:precondition and postcondition
补充:严格的说,doHealthValue()可以不是private属性

                  (3).              The Strategy Pattern via Function Pointer (策略模式)
class GameCharacter; //forward declaration
int defaultHealthClac(const GameCharacter& gc);
class GameCharacter
{
public:
   typedef int (*HealthCalcFunc)(const GameCharacter&);
   explicit GameCharacter(HealthCalc hcf = defaulHealthCalc)
   :healthFnc(hcf) {}
   int healthValue() const
   { return healthFunc(*this); }
private:
   HealthCalcFunc healthFunc;
};
使用GameCharacter
class EvilBadGuy:public GameCharacter
{
public:
   explicit EvilBadGuy(…):….{}  // constructor
}
int loseHealthQuickly(const GameChararcter&);
int loseHealthSlowly(const GameChararcter&);
EvilBadGuy ebg1(loseHealthQuickly);
EvilBadGuy ebg2(loseHealthSlowly);
优点:同一角色(EvilBadGuy),不同的计算公式
缺点:loseHealthQuickly等非类成员函数,不能访问类私有成员,当然访问类
      私有成员,可以让类提供公有成功getter and setter。
      如何访问类私有成员,在这里是程序员的职责。下述代码亦属非成员

                  (4).              The Strategy Pattern via tr1::function
通过函数对象,的确是个不错的方法
class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter
{
public:
   typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;
   explicit GameCharacter(HealthCalcFunc hcf = defaultCalc)
   :healthFunc(hcf)  {}
   int healthValue() const
   { return healthFunc(*this); } //函数对象调用重载的()操作符函数
private:
   HealthCalcFunc healthFunc;
};
使用方法:
short calcHealth(const GameCharacter&);
struct HealCalculator  //声明结构体,并定一个变量HealthCalculator
{
   int operater()(const GameCharacter&) const {…..}
};
class GameLevel
{
public:
   float health(const GameCharacter&) const;    //一个类的成员函数
};
class EvilBadGuy:public GameCharacter {…};  //ditto
EvilBadGuy ebg1(calcHealth);
EvilBadGuy ebg2(HealthCalculator());
GameLevel currentLevel;
EvilBadGuy edb3(
   std::tr1::bind(&GameLevel::health,  currentLevel,  _1);
)
优点:可以隐式转换输入参数,可以隐式转换输出结果,例如calcHealth返回
      short类型。而bind的作用:由于GameLevel::health需要两个参数,this
      指针和GameCharacter&,使用currentLevel提供了this参数,这样就只
      需要另一个参数了GameCharacter&,即变成了需要一个参数的函数。
      简单的说GameLevel::health需要两个参数,通过bind,提供了其中的
      一个参数,使GameLevel::health需要一个参数,满足了EvilBadGuy的
      构造函数的接口要求。

                  (5).              The “Classic” Strategy Pattern
如果你更了解设计模式,你可能设计如图(turn to Page 176),这么无法提供图片,所以不能更深入的用语言做出总结,请阅书

5           Never redefine an inherited non-virtual function

                  (1).              class B
{
public:
   void mf();
};
class D:public B
{
public:
   void mf();   //覆盖
};
D x;
B *pB = &x;  //多态
pB->mf();  //调用B::mf()
D *pD = &x;
pD->mf();   //调用 D::mf()
想必上面大家都很明白,为什么作者禁止在D中重定义mf()呢?
从面向对象的思想来分析:任何适用于B的一定适用与D,非虚成员mf,适用于B,那也一定适用于D,而D需要自己的mf,说明B的mf不适用,矛盾。如果B允许D改写有mf,就必须声明为mf,即说明对于mf,一部分适用,一部分不适用。

6           Never redefine a function’s inherited default parameter value.

                  (1).              正如上一节所述,重定义基类的非虚函数是不允许的,在此基础上,讨论虚函数的重定义
class Shape
{
public:
   enum ShapeColor{Red, Green, Blue};
   virtual void draw(ShapeColor color=Red) const = 0;
};
class Rectangle: public Shape
{
public:
   virtual void draw(ShapeColor color = Green) const;
};
使用:Shape *ps = new Rectangle;   ps->draw();
      结果是使用Red来draw(),最自然的解释是,默认值是静态联编,而
      virtual是动态联编,当然编译也可以动态联编实现默认值,这必将浪费
      运行时的空间与时间,最终编译器选择静态联编,所以才出现该怪现象

                  (2).              那如何在需要重定义virtual成员时,指定一个默认参数呢?例如上的Red
class Shape
{
public:
   enum ShapeColor{Red, Green, Blue};
   virtual void draw(ShapeColor color=Red) const = 0;
};
class Rectangle: public Shape
{
public:
   virtual void draw(ShapeColor color = Red) const;
};
问题:没添加一个派生类,都要指定Red默认值,当Red默认值修改成Green,
     所以派生类的重定义都需要修改
解决方案:non-virtual interface idiom(NVI idiom)
class Shape
{
public:
   enum ShapeColor{Red, Green, Blue};
   void draw(ShapeColor color=Red) const
   {
      doDraw(color);
   }
private:
   virtual void doDraw(ShapeColor color) const = 0;
};
class Rectangle: public Shape
{
private:
   virtual void doDraw(ShapeColor color) const;
};

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

                  (1).              组成由两种意思has a 和 is-implemented-in-terms-of
class Address{ … };
class Person
{
private:
   std::string name;
   Adress address;
};
此时可以说Person由string和Adress组成

                  (2).              什么时候表示has a关系,什么时候表示is-implemented-in-terms-of
When composition occurs between objects in the application domain, it expresses a has relationship. When it occurs in the implementation domain, it expresses an is-implemented-in-terms-of relationship.

                  (3).              Composition is also known as layering, containment, aggregation, and embedding

                  (4).              STL 中的set模板,采用了balanced search trees数据结构,所以它的查找,插入和擦除的复杂度是对数倍数。它提高了速度(即时间),但增大了空间的使用(需要三个指针)。假设我们的空间比时间更重要,需要实现这样的set,让它利用list,list是链表结构,只需要一个指针即可。如何利用list呢?
is-a关系:template<typename T> class set: public std::list<T>{…}; 它要求能以应用于list就能应用于set,list允许重复,而set不允许,显然is-a不行
composition:既然is-a不行,那只能是composition了,composition有两种含义,分别是has a 和 is-implemented-in-terms-of。has a 显然不行,一个set中有一个list?那只能是is-implemented-in-terms-of。实现如下:
template <class T>
class set
{
public:
   bool member(const T& item) const;
   void insert(const T& item);
   void remove(const T& item);
   std::size_t size() const;
private:
   std::list<T> rep;
};
定义如下:
template<typename T>
bool set<T>::member(const T& item) const
{
   return std:find(rep.begin(), rep.end(), item) != rep.end();
}
template<typename T>
void set<T>::insert()(const T& item)
{
   if(!member(item)) rep.push_back(item);
}
template<typename T>
void set<T>::remove(const T& item)
{
   typename std::list<T>::iterator it = std::find(rep.begin(), rep.end(), item);
   if(it != rep.end()) rep.erase(it);
}
template<typename T>
std::size_t set<T>::size() const
{
   return rep.size();
}
说明:从set的用户角度看,完全隐藏了list的细节。通常符合STL的习惯(如
     size,remove等方法名),往往也符合了interface that are easy to use
     correctly and hard to use incorrectly

8           Use private inheritance judiciously

                  (1).              class Person{ … };
class Student:private Person{ … };
void eat(const Person& p);
void study(const Student& s);
Person p;
Student s;
eat(p);
eat(s);  //error
问题:为什么eat(s)会编译报错呢?
解释:首先分析private继承,究竟发生了什么?第一,当发现private继承,
      编译器将拒绝将派生对象转换为基类对象。第二,所有基类的任何属性
      的成员,变成了派生类的私有成员。
      eat(s);错误的原因就是编译不将Student转换成Person,于是报错
结论:所以private继承,属于is-implement-in-terms-of,而没有所谓的is-a或
      者has-a等面向对象设计的思想,仅仅是技术实现

                  (2).              Composition也有is-implement-in-terms-of,如何在private与composition中作出抉择呢?
Use composition whenever you can, and use private inheritance whenever you must. Primarily when protected members and/or virtual functions enter the picture. (但有特例,一会介绍)

                  (3).              举例讨论,假设我们实现一个小窗体控件,该控件需要定时得显示统计数据(比如被点击的次数等等)。
很容易联想到Timer类
class Timer
{
public:
   explicit Timer(int tickFrequency);
   virtual void onTick() const;  // called for each tick
};
Timer类的onTick的实现不是我们所需要的,因此我们需要改写它。同样是is-implement-in-terms-of,composition并不能够改写onTick。
class Widget:private Timer
{
private:
   virtual void onTick() const;   //显示统计数据
};
解释:onTick()的属性声明为私有,是为了与private继承基类的onTick()属性
      一致。同时onTick也不是Widget愿意提供给客户的接口。当然也可以
      声明为public,但是接口设计中提到,make interfaces easy to use correctly
      and hard to use incorrectly

                  (4).              (2)中提到Use composition whenever you can, and use private inheritance whenever you must. 如何用composition实现呢?
class Widget
{
private:
   class WidgetTimer:public Timer
   {
   public:
      virtual void onTick() const; //达到了改写onTick实现的目的
   };
   WidgetTimer timer;
};
上诉代码比private继承更复杂,有内部类,public继承,组合等技术。既然用到它,它有什么优点呢?

1st.    如果Widget不想onTick的实现被修改,如何实现呢?
如果有Java的final或者C#的sealed那就好办了,可是没有。
使用private继承,将onTick声明为私有属性,不可以避免它的派生类可以修改私有的虚成员onTick()。
使用上述技术可以实现类似Java中的final效果

2nd.  上述技术可以最小化Widget的编译依赖,但需要小部分修改。如果使用private继承,必须要得到Timer的定义,这就是依赖于定义。记得之前提到最小化依赖提到的两个技术:pimpl idiom与接口类技术。如果将WidgetTimer定义提到外部,让Widget声明一个WidgetTimer指针,因此Widget只依赖于WidgetTimer的声明,而不依赖WidgetTimer的定义。所以不管WidgetTimer如何依赖于Timer与Widget无关。

                  (5).              在(2)提到特例
如果一个类,no virtual functions(需要虚拟函数指针),且不是virtual继承,且no non-static members,那这个类对象的空间大小应该为0。
可是,C++的生成对象的过程是,分配内存,然后初始化。例如new Object,分配内存0,然后返回指针,显然不合适。于是在C++,它分配的大小是1。即最小的分配单位。
class Empty{};
sizeof(Empty) 会等于 1
class HoldsAnInt
{
private:
   int x;
   Empty e;
};
sizeof(HoldsAnInt)等于5
如果空间在程序中很重要的话,虽然只有1byte,需要避免这个问题:
class HoldsAnInt:private Empty
{
private:
   int x;
};
sizeof(HoldsAnInt)等于4
继承可以避免这个问题,编译器会对从Empty继承作出如上优化,成为empty base optimization EBO. 值得注意的是,EBO只能在单继承下可行。

                  (6).              同样实现is-implement-in-terms-of,组合也更加好理解,尽量使用组合,如果非private不可,一定要给出充分,明智的理由。

9           Use multiple inheritance judiciously.

                  (1).              多继承常见的问题之一,是从多个基类继承同样的成员名称
class BrrowableItem
{
public:
   void checkOut();
};
class ElectroncGadget
{
private:
   bool checkOut() const;
};|
class MP3Plaryer:public BorrowableItem, public ElectronicGadget{…};
MP3Player mp;
mp.checkOut();
问题:编译器报错,即使checkOut的属性,是否const不同,但是编译认为它
      们是一致的,找到最匹配,但并不唯一的成员,所以产生二义性错误
解决方案:需要指明checkOut所在的作用域,mp.BorrowableItem::checkOut();

                  (2).              多继承中另一个常见的问题:从多个路径继承同一个基类多次
class File{…};
class InputFile:public File{…};
class OutputFile:public File{…};
class IOFile:public InputFile, public OutputFile {…};
问题:当File有一个filename成员,IOFile将分别从InputFile与OutputFile
      两条路径继承filename成员,即有两个filename成员
解决方案:虚继承
class File{…};
class InputFile:virtual public File{…};
class OutputFile:virtual public File{…};
class IOFile:public InputFile, public OutputFile {…};
问题:既然虚继承有这么好的优点,那么所有继承都用虚继承,不是很好。想
     法不错,但是虚继承的速度,空间,初始化与赋值的复杂度等成本太大,
     一个虚继承的对象比没有非虚继承的对象要大。所以使用虚拟需要有一
     定的原则:First, don’t use virtual bases unless you need to. Second, if you
     must use virtual base classes, try to avoid putting data in them.
说明:Java与.NET中没有多继承,但支持实现多个接口,接口没有数据成员,
     即符合了上述的Second

第七章    Templates and Generic Programming
template metaprogramming的(算是)概念:the creation of programs that execute inside C++ compilers and that stop running when compilation is complete. 在你理解了编译器与模板之间的关系之后,理解这个概念就容易了。

1           Understand implicit interfaces and compile time polymorphism.

                  (1).              The world of object-oriented programming revolves around explicit interfaces and runtime polymorphism.
The world of templates and generic programming, explicit interfaces and runtime polymorphism continue to exist, but they’re less important. Instead, implicit interfaces and compile-polymorphism move to the fore.

                  (2).              显示接口与多态是如何给出的呢?
class Widget
{
public:
   Widget();
   virtual ~Widget();
   virtual std::size_t size() const;     //virtual 给出多态
   void swap(Widget& other);       //给出接口
};
隐式接口与多态,又是如何给出的呢?
template<typename T>   //给出多态,根据不同的类型进行实例化
void doProcessing(T& w)
{
   if (w.size() > 10)  //给出size接口
   {
      T temp(w);
      temp.swap(w);   //给出swap接口
   }
};

2           Understand the two meanings of typename.

                  (1).              template<class T>class Widget;
tempate<typename T>clss Widget;
这里的typename与class意思一致

                  (2).              概念说明:Name in a template that are dependent on a template parameter are called dependent names. Call others non-dependent name. When a dependent name is nested inside a class, I call it a nested dependent name.
template<typename T>
void doSomething(const T& container)
{
   T::const_iterator iter(container.begin());
   ……        //T::const_iterator is a nested dependent name
}
问题:这种nested dependent name往往给编译器带来解析的困难,例如
      template<typename T>
      void doSomething(const T& container)
      {
         C::const_iterator *x;
      }
      如果C::const_iterator是一个类型,x就是该类型的指针变量
      如果C::const_iterator是一个成员(nested dependent name),而x又刚好
      是全局变量,编译器的执行结果是两者相乘。
      C++编译器默认情况认为,C::const_iterator是一个成员变量,此时编译
      器将不会去模板定义中去搜索。当给C::const_iterator加上typename前
      缀后,编译器将搜索模板定义,确定究竟是类型还是成员变量。

                  (3).              所以(2)的结论就是遇到nested dependent name时,请记得使用typename。但有例外
template<typename T>
class Derived:public Base<T>::Nested  //Nested是nested dependent name,但
{                                 //不需要typename
public:
   explicit Derived(int x)
   :Base<T>::Nested(x);             //ditto
   {
      typename Base<T>::Nested temp;  //需要typename
   }
};
例外就是:当nested dependent name在基类列表或者初始化列表中就不需要
          typename

                  (4).              typename的规则有时候会随不同的编译而异,常使用typedef技术,例如:
template<typename IterT>
void doSomething(IterT iter)
{
   typedef typename std::iterator_traits<IterT>::value_type  value_type;
   value_type temp(*iter);
}
说明:typedef的使用,可以减少字符的键入,同时可随编译器的不同进行快
      速的调整是否使用typename。这在程序移植中是非常重要的。

3           Know how to access names in templatized base classes.

                  (1).              Class EncryptMethodA{……};
class EncryptMethodB{……};
template<typename EncryptMethod>
class MsgSender
{
public:
   void sendClear(const MsgInfo& info)   //发送明文 clear text
   {
      std::string msg;
      create msg from info.
      EncryptMethod enc;
      enc.sendCleartext(msg);     
   }
   void sendSecret(const MsgInfo& info) //发送密文,具体ditto
};
现将模板应用于继承:
template<typename EncryptMethod>
class LoggingMsgSender:public MsgSender<EncryptMethod> //发送前后记日志
{
public:
   void SendClearMsg(const MsgInfo& info)
   {
      log before
      sendClear(info);  //无法通过编译
      log after
   }
};
说明:为什么无法通过编译呢?简单的说:编译器在遇到模板继承时,拒绝向
     基类搜索。即这里只搜索LoggingMsgSender的作用域,并不搜索
     MsgSender的作用域,于是sendClear未定义。那为什么编译不搜索
     MsgSender的作用域呢?主要是因为模板存在具体化,例如:
     假设,现在为加密算法C提供模板具体化
     class EncrypMethodC
     {
     public:
        void sendEncrypted(const std::string& msg);  // only Encrypted version
     };
     template<>
     class MsgSender<EncryptMethodC>
     {
        void sendSecret(const MsgInfo& info){ … }
     };
     此时通过模板定义,无法判断MsgSender的sendClear是否有效,要根
     据实际的实例化,判断。当以EncrypMethodC实例化,则sendClear不
     存在,当以非EncrypMethodC实例化,则sendClaer存在。
补充:为什么LoggingMsgSender使用SendClearMsg接口,而不使用sendClear
     接口,是因为sendClear是非virtual成员,不允许重定义,前面也讲过
     也要避免隐藏。

                  (2).              介绍(1)中的三种解决方案

1st.    this->sendClear(info)

2nd.  using MsgSender<EncryptMethod>::sendClear;

3rd.   MsgSender<EncryptMethod>::sendClear //注意:它取消了virtual动态联编

4           Factor parameter-independent code out of templates

                  (1).              If you’re not careful, using templates can lead to code bloat

                  (2).              template<typename T, std::size_t n>
class SquareMatrix
{
public:
  void invert();
};
使用:
SquareMatrix<double, 5> sm1;
sm1.invert();
SquareMatrix<double, 10> sm2;
sm2.invert();
问题:编译器将为<double, 5>和<double, 10>分别实例化,是否可以减少目标
      代码呢?
补充:invert()与矩阵的数据类型有关系,并且与矩阵的大小有关系

                  (3).              Template<typename T>
class SquareMatrixBase
{
protected:
   void invert(std::size_t matrixSize);
};
template<typename T, std::size_t n>
class SquareMartix:private SquareMartixBase<T>
{
private:
   using SquareMatrixBase<T>::invert;  //why using see below item
public:
   void invert()
   { this->invert(n); }  //why this see below item  inline
}
说明:该方案还未完成,是SquareMatrixBase的invert未能实现,它的任务是
      反转矩阵,所以它需要获取矩阵数据。但不防先说说它是如何减少目标
      代码大小的。<double, 5>和<double, 10>将SquareMatrix模板实例化2
      次,但SquareMatrixBase仅仅依赖于T,即double,因此仅被实例化1
      次。
补充:此时SquareMatrix<double, 5>与SquareMatrix<double, 10>属于不同的类
      型,它们各自的对象之间是不允许传递,赋值的。

                  (4).              比较(2)与(3)的优点:

1st.    (2)版本,invert针对的矩阵大小是固定的,通常编译器会给出优化。而(3)版本,invert针对的矩阵大小是个变量。

2nd.  (3)版本中虽然invert针对的矩阵大小是个变量,但它的代码就一份,如果它在一段时间内,经常被访问,且又由于计算机中往往实现了指令cache。提高了指令cache的命中率(hit rate),就提高了程序的运行速度。

                  (5).              下面提供实现的代码,但对本项的讨论没有任何意义
让SquareMatrixBase持有矩阵的数据,通常是拥有SquareMatrix中矩阵的指针。
template<typename T>
class SquareMatrixBase
{
protected:
   SquareMatrixBase(std::size_t n, T *pMem)
   :size(n), pData(pMem){}
   void setDataPtr(T *ptr) {pData = ptr; }
private:
   std::size_t size;
   T *pData;
}
template<typename T, std::size_t n>
class SquareMatrix:private SquareMatrixBase<T>
{
public:
  SquareMatrix()
  :SquareMatrixBase<T>(n, data){};
private:
   T data[n*n];
};
上述的data也可以使用动态内存,代码就不给出了

5           Use member function templates to accept “all compatible types.”

                  (1).              Use member function templates to generate functions that accept all compatible types
class Top {…};
class Middle:public Top {…};
class Bottom:public Middle {…};
Top *pt1 = new Middle;
Top *pt2 = new Bottom;
const Top *pct2 = pt1;
用户自定义类型的指针,支持如上的指针类型转换。那模板的指针也应该实现该类型转换,如何实现呢?
template<typename T>
class SmartPtr
{
public:
explicit SmartPtr(T *realPtr);
};
SmartPtr<Top> pt1 = SmartPtr<Middle>(new Middle);
SmartPtr<Top> pt2 = SmartPtr<Bottom>(new Bottom);
SmartPtr<const Top> pct2 = pt1;
问题:Top,Middle,Bottom之前存在着继承的关系,它们间指针的复制属于
      多态。而SmartPtr<Top>,SmartPtr<Middle>与SmartPtr<Bottom>之间就
      像甲与丙没有任何关系。它们之间的赋值是没有任何意义的。

                  (2).              解决方案:
template<typename T>
class SmartPtr
{
public:
   SmartPtr(SmartPtr<Top> topPtr);
   SmartPtr(SmartPtr<Middle> middlePtr);
   SmartPtr(SmartPtr<Bottom> bottomPtr);
};
问题:这种方式扩展性差,当class BelowBottom:public Bottom {…};,又要
      添加一个构造函数SmartPtr(SmartPtr<BelowBottom> belowBottomPtr);

                  (3).              解决方案:模板类中再提供模板成员函数
template<typename T>
class SmartPtr
{
public:
   template<typname U>
   SmartPtr(cosnt SmartPtr<U>& other);
};
问题:上述代码并完全符合(1)中的要求。这里任何的SmartPtr模板的实例之
      间都可以相互转换。例如SmartPtr<A> p(SmartPtr<B>);,而A,B之间
      没有任何关系。而(1)中要求实例之间需要满足继承关系。

                  (4).              解决方案:通过实现get,使用成员初始化列表实现
template<typename T>
class SmartPtr
{
public:
   template<typname U>
   SmartPtr(cosnt SmartPtr<U>& other)  //call it Generalize copy constructor
   :heldPtr(other.get())  {…}
   T* get() const {return heldPtr;}
private:
   T *heldPtr;
};
拷贝构造函数已实现,赋值函数的实现类似。转成员初始化列表到函数内部。

                  (5).              Declare a generalized copy constructor (a member template) in a class doesn’t keep compliers from generating their own copy constructor ( a non template).
template<typename T>
class SmartPtr
{
public:
   SmartPtr(SmartPtr const& ptr);
   template<typname U>
   SmartPtr(cosnt SmartPtr<U>& other);
};

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

                  (1).              Template<typename T>
class Rational
{
public:
   Rational(const T& numerator = 0, const T& denominator = 1);
   const T numerator() const;
   const T denominator() const;
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs)
{……}
该例,想模仿第四章,7实现隐式类型转换,因为构造函数提供默认参数。
调用:Rational<int> oneHalf(1, 2);
      Rational<int> result = oneHalf*2; //无法编译,更别说2*oneHalf
                                 //不过该例它能编译,2*oneHalf也能
问题:很可惜,无法通过编译。原因是编译器,在遇到oneHalf是,得到它的
      类型是Rational<int>,在解析2时,它并不是Rational<int>类型。如果
      生成operator*的实例,也需要确认Rational<int>支持隐式转换。这种任
      务对编译器说是艰巨的,于是编译器选择,在应用上模板时,取消这种
      类型转换。

                  (2).              解决方案:定义个友元成员
template<typename T>
class Rational
{
public:
   friend const Rational operator*(const Rational& lhs, const Rational& rhs);
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs)
{……}
注意:在模板声明中Rational<T>简写成Rational
问题:能编译,无法链接。原因是友元成员的声明,使编译通过,在链接时,     
      需要搜索该接口的定义,我们想利用模板生成定义,可惜失败了,原因
      同上

                  (3).              解决方案:给友元提供定义。
template<typename T>
const Rational<T> doMultiply( const Rational<T>& lhs, const Ration<T>& rhs);
template<typename T>
class Rational
{
public:
   friend const Rational operator*(const Rational& lhs, const Rational& rhs)
   {
      return doMultiply(lhs, rhs);
   }
};
解释:用友元接口来实现隐式类型转换,用doMutiply来实现模板。Nice,但
      是operator操作符,没有被重载,会不方便。不过通常operator*的定义,
      都是提供内联定义。

7           Use traits classes for information about types.

                  (1).              基本知识:STL中包含有容器,迭代器,算法,还有工具模板。其中一种工具叫做advance,它实现将iterator移动制定的距离,类似:
template<typanem IterT, typname DistT>
void advance(IterT& iter, DistT d);
5种迭代器类型:
struct input_iterator_tag{};
struct output_ieterator_tag{};
struct forward_iterator_tag::public input_iterator_tag{};
struct bidirectional_iterator_tag:public forward_iterator_tag{};
struct random_access_iterator_tag:public bidirectional_iterator_tag{};

                  (2).              在(1)的基础上,我们提出,如何实现advance呢?很直接会想到,首先通过判断迭代器类型,然后根据不同的类型,进行不同的操作。
特征类的出现,实现iterator traits后它可以识别iterator类型。特征类还要求能应用于build-in type。
如何识别iterator traits呢?要求有个typedef iterator_category,例如:
template<…>
class deque
{typedef random_acess_iterator_tag  iteracotr_category}
class list
{typedef bidirectional_iterator_tag  iterator_category};
这样就可以统一使用iterator_category来获取iterator traits的iterator类型了。
于是可定义
template<typename IterT>
struct iterator_traits
{
   typedef typename IterT::iterator_category  iterator_category;
};
但是iterator_traits还未能识别build-in类型,可以用局部具体化来解决
template<typename IterT>
struct iterator_traits<IterT*>
{
   typedef random_acess_iterator_tag  iteracotr_category;
};
特征类是一种技术,至少这里它可以应用于识别类型

                  (3).              有了特征类之后,具体实现advance
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
   if (typeid(typename std::iterator_traits<IterT>::iterator_category) ==
      typeid(std::random_access_iterator_tag))
   {…..}
   else if
   else  …
}
问题:编译问题,将在下一节中解释。其次typename std::iterator_traits<IterT>::iterator_category类型在编译期间就可获取,即在编译器期间就可以判断if语句的真假,却为什么要在程序运行时去判断呢?

                  (4).              改善方案:
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag)
{  iter += d; }
void doAdvance(IterT& iter, DistT d, std::bidirectional_iterator_tag)
{
   if (d>=0)  { while(d—) ++iter; }
   else { while(d++) –iter; }
}
void doAdvance(IterT& iter, DistT d, std::input_iterator_tag)
{
   if(d<0){ throw std::out_of_range(“Negative distance”)};
   while(d--) ++iter;
}
最后定义统一接口:
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
   doAdvance(iter, d, std::iterator_taints<IterT>::iterator_category())
}
说明:iterator_tag存在一定的继承关系,编译器会进行判断,选择最匹配的。
      不知道为什么有括号。

8           Be aware of template metaprogramming(TMP)

                  (1).              TMP优点:有时用TMP实现更简单
           编译期间执行,合理设计下,通常效率更好
     缺点:编译时间长

                  (2).              上一节中,有个编译问题,还未进行解释
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
   if (typeid(typename std::iterator_traits<IterT>::iterator_category) ==
      typeid(std::random_access_iterator_tag))
   {…iter += d;..}
   else if
   else  …
}
调用:advance(std::list<int>::iterator& iter, ind)
问题:因为list的迭代器类型是bidirectional_iterator_tag类型,因此不能执行
      iter+=d; 虽然我们知道该语句不会被执行,可是编译不知道,它需要所
      有应用它的必须合法。

                  (3).              在TMP中没有真正的循环构造,往往通过递归实现。介绍一个TMP的基础例子,就像其他语言中的hello world。让大家对TMP更加熟悉。
template<unsigned n>
struct Factorial    //翻译:阶乘
{
   enum{ value = n* Factorial<n-1>::value  };
};
template<>
struct Factorial<0>
{
   enum {value=1;};
};
调用:
int main()
{
   std::out << Factorial<5>::value;    //120
   std::out << Factorial<10>::value;   //3628800
}
说明:阶乘的计算是在编译期间运算的,不占用运行时间

                  (4).              为了理解TMP的价值,及其它的应用,作者讲述了三个例子

1st.    Ensuring dimensional unit correctness
TMP可以支持编译期间发现错误

2nd.  Optimizing matrix operations
利用前面讲过的重载operator*操作符和SquareMatrix,
typename SquareMatrix<double, 10000> BigMatrix;
BigMatrix m1, m2, m3, m4, m5;
BigMatrix result = m1*m2*m3*m4*m5;
如果这在编译期间运行,那效率就高很多了

3rd.   Generating custom design pattern implementations
使用模板实现各种模式称作policy-based design。然后根据选择,编译器生成不同的模式代码

第八章    Customizing new and delete

1           Understand the behavior of the new-handler

                  (1).              老编译器,new操作符失败返回null,新编译器通常抛bad_alloc异常。但在抛异常之前,先调用new-handler。可以通过std::set_new_handler来指定需要的处理。
namespace std
{
   typedef void (*new_handler)();
   new_handler set_new_handler(new_handler p) throw();
}
说明:throw()指出,该函数不允许抛异常,如果抛异常则属严重的错误

                  (2).              如何设计好的new-handler处理函数呢?

1st.    Make more memory available
即尽量保证new函数正常返回

2nd.  Install a different new-handler
即在需要时,设置所需的new-handler

3rd.   Deinstall the new-handler
即不安装任何的new-handler,即set-new-handler参数为null

4th.   No return
通常是调用abort或exit退出程序

                  (3).              有时你需要为不同的类,为new该类型对象时设置不同的new-handler。
C++并未直接提供机制支持,但我们可以代码实现。
class Widget
{
public:
   static std::new_handler set_new_handler(std::new_handler p) throw();
   static void * operator new(std::size_t size) throw(std::bad_alloc);
private:
   static std::new_handler currentHandler;
};
复习:静态类成员需要在类外定义。除非它是const且是整型。以下给出定义:
std::new_handler Widget::currentHandler = 0;
std::new_handler Widget::set_new_handler(std::new_handler p) throw()
{
   std::new_handler oldHandler = currentHandler;
   currentHandler = p;
   return oldHandler;
}
接下来需要定义operator new了,给出主要步骤:
1.调用set_new_handler,设置currentHandler
2.调用全局new,分配内存
3.(1)如果它失败会抛出异常,但在抛出异常之前会调用new-handler,最后恢
    复new-handler
3.(2)如果成功,返回void*,恢复new-handler
问题:如何恢复new_handler,即在set_new_handler时保存返回的new_handler

                  (4).              解决方案:资源管理类。为了给出operator new的定义,我们需要资源管理类
class NewHandlerHolder
{
public:
   explicit NewHandlerHolder(std::new_handler nh):handler(nh) {}
   ~NewHandlerHolder(){ std::set_new_handler(handler); }
private:
   std::new_handler handler;
   NewHandlerHolder(const NewHandlerHold&); //prevent copying
   NewHandlerHolder operator=(const NewHandlerHolder&);
};
终于可以给出operator new的定义了:
void * Widget::operator new(std::size_t size) throw(std::bad_alloc)
{
   NewHandlerHolder h(std::set_new_handler(currentHandler));
   return ::operator new(size);
}
当h跳出Widget::operator new作用域时,调用析构函数,恢复new_handler。

                  (5).              可能我们会想开发出实现上述功能的基类,或模板基类用于继承。我们需要继承类成员set_new_handler,operator new,currentHandler。
template<typename T>
class NewHandlerSupport
{
public:
   static std::new_handler set_new_handler(std::new_handler p) throw();
   static void * operator new(std::size_t size) throw(std::bad_alloc);
private:
   static std::new_handler currentHandler;
};
template<typename T>
std::new_handler
NewHandlerSupport<T>::set_newhandler(std::new_handler p) throw()
{
   std::new_handler oldHandler = currentHandler;
   currentHandler = p;
   return oldHandler;
}
template<typename T>
void *
NewHandlerSupport<T>::operator new(std::size_t size) throw(std::bad_alloc)
{
   NewHandlerHolder h(std::set_new_handler(currentHandler));
   return ::operator new(size);
}
template<typename T>
std::new_handler NewHandSupport<T>::currentHandler = 0;
调用:
class Widget:public NewHandlerSupport<Widget> { … };
说明:虽然NewHandSupport的声明与定义没有依赖于类型,却将它定义为模
      板,为何?这是为了实现不同的类拥有不同的静态成员currentHandler。
      如果不使用模板,所有的派生类,会共用currentHandler吗?这种技术
      被叫做CRTP,curiously recurring template pattern

                  (6).              直到1993年,C++要求,new失败返回null,直到最近,C++要求,new失败,抛出异常,而C++又不愿抛弃,之前依赖null的代码,例如test for null。
于是提供了nothrow版本,使用如下:
Widget *p = new (std::nothrow) Widget;
if (p == 0) …
说明:new (std::nothrow) Widget; 由于Widget有自己的new与new-handler,
      是否真正不抛出异常,需要看Widget

2           Understand when it makes sense to replace new and new

                  (1).              上一节介绍了,operator new失败调用new-handler,并介绍了set_new_handler,修改new-handler。这节介绍如何替代operator new,与上节不同,它是替代全局operator new 分配内存的方式。
作者给出了三个常见替换operator new 的理由:

1st.    To detect usage errors
当new分配内存成功后,调用delete,如果失败,将导致内存泄露,当调用detele两次,针对同一地址,将导致不确定行为。还有比如超出上界或下界,破坏了数据等行为。此时可以替换operator new,提供有效的处理。

2nd.  To improve efficiency
通用的全局operator,它是应通用目的设计的,因此在速度,空间上都不是最优的。

3rd.   To collect usage statistics
例如:统计当前应用,总共申请了多少动态内存。在程序运行期间,动态内存的峰值等。

                  (2).              理论上说,替代operator new很简单:
static const int signature = 0xDEADBEEF;
typedfe unsigned char Byte;
void* operator new(std::size_t size) throw(std::bad_alloc)
{
   using namespace std;
   size_t realSize = size + 2*sizeof(int);
   void *pMem = malloc(realSize);
   if(!pMem) throw bad_alloc();
   *(static_cast<int*>(pMem)) = signature;
   *(reinterpret_cast<int*>(static_cast<Byte*>(pMem)+realSize-sizeof(int))) =
    signature;
   return satic_cast<Byte*>(pMem) + size(int);
}
问题:all operator news should contain a loop calling a new-handling function.
      上一节的operator new调用了全局的operator new,故满足。这个问题
      我们下一节关注。
另一问题:内存布局,alignment。在某些计算机体系中,int是4字节对齐,
          double是8字节对齐。如果此时operator new为double分配内存,
          返回地址+4,可能带来内存布局问题。有些系统将导致错误,有
          些系统仅仅是靠多次访问内存,从而降低了效率。
所以理论上,替换operator new是简单的,但替换后提供一个能够较好工作的operator new就困难多了。

                  (3).              不过幸运的是,通常编译器提供多种operator new,应用不同的需要,不过需要熟悉编译器的配置,你可能需要去阅读编译器文档。配置后,你需要的做的只是relink。
另一个选择是,选择open source memory managers. 例如Boost。开源的伟大。

                  (4).              最后给出,什么情况下需要考虑替换operator new and delete,具体如何考虑。

1st.    To detect usage errors
当new分配内存成功后,调用delete,如果失败,将导致内存泄露,当调用detele两次,针对同一地址,将导致不确定行为。还有比如超出上界或下界,破坏了数据等行为。此时可以替换operator new,提供有效的处理。

2nd.  To collect usage statistics
例如:统计当前应用,总共申请了多少动态内存。在程序运行期间,动态内存的峰值等。

3rd.   To increase the speed of allocation and deallocation.
通常通用目的的全局operator,在速度与空间上并不是最优的。例如:当你的应用是单线程的,而全局提供的是thread-safe版本的,此时你可以选择thread-unsafe版本,以提高效率

4th.   To reduce the space overhead of default memory management

5th.   To compensate for suboptimal alignment in the default allocator
例如:当在X86平台上,double是8字节的对齐的,而你的编译器提供
      的operator new非针对8字节对齐而设计的,此时可以考虑替换它。

6th.   To cluster related objects near one another.
比如:你需要一些对象,它们的内存位置,相对比较集中,在频繁访问时
      少出现缺页,以提供效率。你可以考虑替换它,后续节会介绍。

7th.   To obtain unconventional behavior.
例如:你需要分配在共享内存中的内存。且只有C API可用,后续节介绍。

3           Adhere to convention when writing new and delete.

                  (1).              我们以举例,逐步提及习惯
void* operator new(std::size_t size) throw(std::bad_alloc)
{
   using namespace std;
   if (size == 0) { size = 1; }
   while (ture)
   {
      attempt to allocate size bytes;
      if (the allocation was successful)
         return (a pointer to the memory);
      new_handler globalHanlder = set_new_handler(0);  //获取原new-handler
      set_new_handler(globalHandler);                //的快速简单的方法
      if(globalHandler) (*globalHandler)();
      else throw std::bad_alloc();
   }
}
说明:C++要求operator new返回合理的指针,即使size=0;因此有if(size==0)
      operator new要求死循环,直到获取内存,或抛出异常,才退出。
      new_handler globalHanlder = set_new_handler(0);  //获取原new-handler
      set_new_handler(globalHandler);
      在单线程中是全局的,如果在多线程中,需要信号量等同步机制帮助

                  (2).              具体类的operator new成员,当该类不在继承体制中,比较简单,因为它的大小固定。而当处于继承体制中,就相对复杂了。
class Base
{
public:
   static void* operator new(std::size_t size) throw(std::bad_alloc);
};
class Derived:public Base { … };
Derived *p = new Derived;
不知道你是否注意,new的参数是size_t,而调用new Derived,我想你会明白。
void* Base::operator new(std::size_t size) throw(std::bad_alloc)
{
   if (size != sizeof(Base))
      return ::operator new(size);
   .. other handle the request here.
}
sizeof(Base)结果不可能为0,即使它是empty class。因此处理了size=0的情况
问题:我还不是很明白,继承时,是如何分配的。为什么if (size != sizeof(Base))。

                  (3).              operator new[]更复杂些,特别是在继承体系中,你并不能用sizeof(Base)确定每个对象的大小,同时你也不能用(bytes requested)/sizeof(Base)来确定数组对象的个数。这在delete[]也会遇到麻烦。所以可能需要多配些内存,保存这些信息。

                  (4).              void operator delete(void rawMemory) throw()
{
   if (rawMemory == 0) return;
  deallocate the memory pointed to by rawMemory
}
C++保证delete null必须是安全的,因此if (rawMemory == 0) return;

                  (5).              类成员的operator delete
class Base
{
public:
   static void operator delete(void *rawMemory, std::size_t size) throw();
};
void Base::operator delete(void *rawMemory, std::size_t size) throw()
{
   if(rawMemeory == 0) return;
   if(size !=sizeof(Base)
   {
      ::operator delete(rawMemory);
      return;
   )
   deallocate other
}
有趣的时,有时size的值不正确,例如基类的析构函数是non-virtual。

4           Write placement delete if you write placement new.

                  (1).              介绍下什么叫做布局new与布局delete的概念。
当operator new声明了必须的size参数外,还声明了其它额外的参数的版本就称为布局new,同理布局delete。最常见的布局new是:
void* operator new(std::size_t, void *pMemory) throw();

                  (2).              Widget *pw = new Widget;
两件事发生:分配动态内存,构造Widget对象。
如果动态内存分配成功,而构造Widget对象时,抛出异常。将造成内存泄露。
如何避免该情况下的内存泄露呢?由用户回收,显然不行,因为此时pw为空。对它调用delete,没有任何作用。所以只能依赖于C++ runtime system。
于是当使用void* operator new(std::size_t) throw(std::bad_alloc);时,编译器会搜索对应的delete,void operator delete(void *rawMemory) throw(); 和
void operator delete(void *rawMemory, std::size_t size) throw();

                  (3).              而当你提供的是布局new,额外参数是io流,用于记录日志,例如:
class Widget
{
public:
   static void* operator new(std::size_t size, std::ostream& logStream)
                                           throw(bad_alloc);
   static void* operator delete(void *pMemeory, std::size_t size) throw();
};
调用:Widget *pw = new(std::cerr) Widget;
仅提供了布局new,没有提供对应的布局delete。当动态内存分配成功,在构造Widget对象时,发生异常,C++ runtime system根据operator new,寻找与它对应的operator delete,发现找不到对应delete,于是不作为,导致内存泄露。解决方案:添加对应的布局new
对应的布局delete格式:void operator delete(void*, std::ostream&) throw();
将它添加之后,在上述情况发生时,将不会出现内存泄露。
注意:如果对象正常构造,此时调用delete pw,使用的标准版本,即
      static void* operator delete(void *pMemeory, std::size_t size) throw(),而不
      是布局delete

                  (4).              C++在全局为我们提供了三个new版本:
void* operator new(std::size_t) throw(std::bad_alloc);
void* operator new(std::size_t, void*) throw();
void* operator new(std::size_t, const std::nothrow_t&) throw();
注意:小心隐藏。
class Base
{
public:
   static void* operator new(std::size_t size, std::ostream& logStream)
                                        throw(std::bad_alloc);
};
Base *pb = new Base;  //error hide global new
Base *pb = new (std::cerr) Base;  //fine, placement new.
解决方案:
class StandardNewDeleteForms
{
public:
   static void* operator new(std::size_t size) throw(std::bad_alloc)
   {  return ::operator new(size);  }
   static void operator delete(void *pMemory) throw()
   {  ::operator delete(pMemory);  }
   //placement new/delete
   static void* operator new(std::size_t size, void*ptr) throw()
   {  return ::operator new(size, ptr);  }
   static void* operator delete(void *pMemeory, void *ptr) throw()
   {  return ::operator delete(pMemory, ptr);  }
   // nothrow new/delete
   static void* operator new(std::size_t size, const std::nothrow_t& nt) throw()
   {  return ::operator new(size, nt)  }
   static void operator delete(void *pMemory, const std::nothrow_t& nt) throw()
   {  ::operator delete(pMemory);  }
};
class Widget
{
public:
   using StandardNewDeleteForms::operator new;
   using StandardNewDeleteForms::operator delete;
   static void* operator new(std::size_t size std::ostream& logStream)
                                       throw(std::bad_alloc);
   static void operator delete(void *pMemory, ostream& logStream) throw();
};

第九章    Miscellany

1           Pay attention to compiler warnings.

                  (1).              Take complier warnings seriously, and strive to compile warnngfree at the maximum warning level supported by you compilers.

                  (2).              Don’t become dependent on complier warnings, because different compilers warn about different things. Porting to a new compiler may eliminate warning message you’ve come to rely on.

2           Familiarize yourself with the standard library, including TR1.

原创粉丝点击