Effective C++-读书笔记

注:本笔记只适用于本人,想怎么写就怎么写,想写多少就写多少,想略过就略过,完全随心所欲。

条款07:多态基类声明virtual析构函数。

C++明白指出,当derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果未有定义-----实际执行时通常发生的是对象的derived成分没被销毁。(说说感想,这条规定是让我觉得不可理喻,很傻逼,即使是一个base class指针它在执行期时也有足够的信息获取它所指向的实际derived class,为什么不直接把derived class部分也处理了呢?)

而且,就算base class中没有带任何virtual函数,直接删除一个指向dervied class实例的base class指针,这样的结果也是没有定义的。也就是说你不能直接继承string,stl等类。因为它们不是用来继承的。

条款08:别让异常逃离析构函数。

这个人人都知道

条款09:绝不在构造和析构过程中调用virtual函数。

这个我知道,因为我看了《C++对象模型》

条款10:令operator=返回一个reference to *this。

(a = b) = c;
为了支持这种赋值。
条款11:operator=中不要自我赋值

Widget& 
Widget::operator=(const Widget& rhs) {
	delete pb; // 这里会傻逼
	pb = new Bitmap(*rhs.pb);
	return *this;
}
条款13:以对象管理资源
也就是RAII(资源获取就是初始化)

条款18:让接口容易被正确使用,不易被误用

class Date {
public:
	Date(int year, int month, int day) {}
}
不如下面的安全
struct Day {
	explicit Day(int d) {}
};

struct Month {
	explicit Month(int m) {}
};

struct Year {
	explicit Year(int y) {}
};

class Date {
public:
	Date(const Year& y, const Month& m, const Day& d) {}
}
条款21:必须返回对象时,别返回引用

返回引用不安全,并且有的时候返回对象,编译器会进行named return value优化,效率会很高哦~

条款22:成员变量声明为private

封装啊,蠢货。如果你通过函数访问成员变量,日后可改以某个计算替换这个成员变量,而class客户一点也不知道class的内部实现已经起了变化。

在封装的眼里只有private和其他限定访问符(protected跟public没区别)。

条款23:宁以non-member,non-friend替换member函数

面向对象守则要求,数据以及操作数据的那些函数应该被绑在一块,这意味着它建议member函数是较好的选择。不幸的是这个建议不正确。这是基于对面向对象真实意义的一个误解。面向对象守则要求数据应该尽可能被封装,然而与直观相反的,member函数clearEverything带来的封装性比non-member函数clearBrowser低。并且non-member函数有较大的包裹弹性。封装是为了让更少的人去看到它,这样我们就能够改变事物而只影响有限客户

条款24:若所有参数皆需类型转换,请为此采用non-member函数

class Rational {
public:
	...
	const Rational operator*(const Retional&rhs) const {}
};

result = oneHalf * 2;	// ok
result = 2 * oneHalf;	// error
把它转换成non-member是不是更好?
const Rational operator*(const Rational& lhs, const Rational& rhs) {
	....
}
条款25:写出一个不抛异常的swap函数
条款26:尽可能延后变量定义式的出现时间

注意注意。

条款27:尽量少做转型动作

class Window {
public:
	virtual void onResize() {}
};

class SpecialWindow:public Window {
public:
	virtual void onResize() {
		static_cast<Window>(*this).onResize(); // 这跟Window::onResize()不是一回事,static_cast<Window>(*this)会产生一个副本,执行的是这个副本的onResize
		...
	}
};
dynamic_cast效率低,要求效率的程序要少用
条款29:为“异常安全”而努力是值得的

条款30:透彻了解inlining的里里外外

inline只是对编译器的一个申请,不是强制命令。这项申请可以隐喻提出,也可以明确提出。把一个函数定义在class定义式内,就是隐喻提出。

Inlining在大多数C++程序中时编译期行为。

Template的具现化与inlining无关。

大部分编译器将太过复杂的函数inlining,而所有对virtual函数的调用也会使inlining落空。

条款31:将文件间的编译依存关系降至最低

在C++中,编译器看到Person p;p的定义就必须知道要分配多少空间放置一个Person。这个问题在java上不存在,因为定义一个java对象时,编译器只分配足够空间给一个指针。

class Person {
public:
  Person(const std::string& name, const Date& birthday, const Address& addr);
  std::string name() const;
  std::string birthDate() const;
  std::string address() const;
private:
  std::string theName;
  Date theBirthDate;
  Address theAddress;
};
这样的定义式通常由#include指示符提供,所以Person定义文件的最上方很可能是这样的东西
#include <string>
#include "date.h"
#include "address.h"

这样就形成一层依赖关系。如果这些头文件有任何改变,那么每一个含入Person class的文件就得重新编译。

只对Person我们可以这样做:把Person分割为2个classes,一个只提供接口,另一个负责实现该接口。

#include <string>
#include <memory>

class PersonImpl;
class Date;
class Address;

class Person {
public:
  Person(const std::string& name, const Date& birthday, const Address& addr);
  std::string name() const;
  std::string birthDate() const;
  std::string address() const;
private:
  std::tr1::shared_ptr<PersonImpl> pImpl;
};
这样的设计之下,Person的客户就完全与Dates, Addresses以及Persons的实现细目分离了。那些classes的任何实现修改都不需要Person客户端的从小编译。这真正是“ 接口与实现分离”。

如果使用object references或object pointers可以完成任务,就不要使用objects。

如果能够,尽量以class声明式替换class定义式。

class Date;     // class声明式
Date today();   // 没问题,这里并不需要
void clearAppointments(Date d);// Date的定义式
为声明式和定义式提供不同的头文件。
请记住:

支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classes和Interface classes。

程序库头文件应该以“完全且仅有声明式”的形式存在。这种做法不论是否涉及templates都适用。

条款34:区分接口继承和实现继承

class Airport {...};
class Airplane {
  virtual void fly(const Airport& destination);
};
void Airplane::fly(const Airport& destination) {
  
}

class ModelA : public Airplane {...}
class ModelB : public Airplane {...}
如果此时又有一个ModelC,但是ModelC的fly默认的目的地并不是Airplane中定义的fly。改进:
class Airport {...};
class Airplane {
public:
  virtual void fly(const Airport& destination) = 0;
protected:
  void defaultFly(const Airport& destination);
};
void Airplane::defaultFly(const Airport& destination) {
  // 缺省行为 
}

class ModelA : public Airplane {
public:
  virtual void fly(const Airport& destination) {
    defaultFly(destination);
  }
}
class ModelB : public Airplane {
public:
  virtual void fly(const Airport& destination) {
    defaultFly(destination);
  }
}
class ModelC : public Airplane {
public:
  virtual void fly(const Airport& destination) {
    .....
  }
}
看起来没有问题,可是程序员还是可能因为剪贴代码而招来麻烦。
class Airport {...};
class Airplane {
public:
  virtual void fly(const Airport& destination) = 0;
};
void Airplane::fly(const Airport& destination) {
  // 缺省行为 
}

class ModelA : public Airplane {
public:
  virtual void fly(const Airport& destination) {
    Airplane::fly(destination);
  }
}
class ModelB : public Airplane {
public:
  virtual void fly(const Airport& destination) {
    Airplane::fly(destination);
  }
}
class ModelC : public Airplane {
public:
  virtual void fly(const Airport& destination) {
    .....
  }
}
这个几乎和前一个设计一模一样,只不过pure virtual 函数Airplane::fly替换了独立函数Airplane::defaultFly。本质上,现在的fly被分割为两个基本要素:其声明部分表现的是接口,其定义部分则表现出缺省行为。

条款35:考虑virtual函数以外的其他选择

藉由Non-Virtual Interface手法实现Template Method模式(与C++ templates并无关联):

class Base {
public:
    int healValue() {
        ... // do something
        doHealValue();
        ... // do something
    }

private:
    virtual void doHealValue() {
        cout << "Base::doHealValue" << endl;
    }
};
NVI手法的一个优点就是可以隐身在“做一些事前工作”和“做一些事后工作”之中。

藉由Function Pointers实现Strategy模式:

class GameCharactor;
int defaultHealthCalc(const GameCharactor& gc);
class GameCharacter {
public:
  typedef int (*HealthCalcFunc)(const GameCharacter&);
  explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf){}
  int healthValue() const {
    return healthFunc(*this);
  }

private:
  HealthCalcFunc healthFunc;
};
这样会有一个问题,就是non-memeber函数如何访问class的non-public成分。唯一能够解决它的办法就是:弱化class的封装。例如class可声明那个non-member函数为friends。

藉由tr1::function完成Strategy模式:

一如之前

class GameCharactor;
int defaultHealthCalc(const GameCharactor& gc);
class GameCharacter {
public:
  // HealthCalcFunc可以是任何“可调用物”,可被调用并接受任何兼容于GameCharacter之物,返回任何兼容于int的东西。
  typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;
  explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf){}
  int healthValue() const {
    return healthFunc(*this);
  }

private:
  HealthCalcFunc healthFunc;
};
我们看看HealthCalcFunc是个什么样的typedef:
std::tr1::function<int (const GameCharacter&)>
这个签名代表的函数是“接受一个reference指向const GameCharacter,并返回int”。和函数指针相比,这个设计几乎相同。唯一不同的是如今GameCharactor持有一个tr1::function对象,相当于一个指向函数的泛化指针。弹性很大:
short calcHealth(const GameCharacter&); // 注意其返回值

struct HealthCalculator {// 函数对象
  int operator()(const GameCharacter&) const {}
};

class GameLevel {
public:
  float health(const GameChacter&) const; // 成员函数
};

class EvilBadGuy: public GameCharacter { // 同前
  
};

class EyeCandyCharacter : public GameCharacter {
  
}

EvilBadGuy ebg1(calcHealth); // 人物1,使用某个函数计算
EyeCandyCharacter ecc1(HealthCalculator()); // 人物2,使用某个函数对象计算
GameLevel currentLevel;
EvilBadGuy ebg2 (           // 人物3,使用某个成员函数计算
  std::tr1::bind(&GameLevel::health, currentLevel, _1)
);
古典的Strategy模式,GameCharacter中内含一个指向HealthCalcFunc(健康计算函数对象)。

条款36:绝不重新定义继承而来的non-virtual函数

条款37:绝不重新定义继承而来的缺省参数值

因为缺省参数值都是静态绑定,而virtual函数----你唯一应该覆写的东西----却是动态绑定的。

什么是静态类型呢?

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;
};
class Circle : public Shape {
public:
  virtual void draw(ShapeColor color) const;
  // 请注意,以上这么写则当客户以对象调用此函数,一定要指定参数值。
  // 因为静态绑定下这个函数并不从其base继承缺省参数值。
  // 但若以指针(或reference)调用此函数,可以不指定参数值,
  // 因为动态绑定下这个函数会从其base继承缺省参数值。
};

Shape *ps;                    // 静态类型为Shape*
Shape *pc = new Circle;       // 静态类型为Shape*
Shape *pr = new Rectangle;    // 静态类型为Shape*

pr->draw();                   // 调用Rectangle::draw(Shape::Red)!而不是Shape::Green
因为虽然pr的动态类型是Rectangle*,所以调用的是Rectangle的virtual函数。Rectangle::draw函数的缺省参数值应该是GREEN,但由于pr的静态类型是Shape*,所以此一调用的缺省参数值来自Shape class而非Rectangle class!
之所以这样做,是为了效率。

解决这个的办法是使用NVI,就是non-virtual interface。见条款36

条款39:明智而审慎地使用private继承

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
条款40:明智而审慎地使用多重继承
慎用。如果你唯一能够提出的设计方案设计多重继承,你应该更努力想一想--------几乎可以说一定会有某些方案让单一继承行得通。然而多重继承有时候的确是完成任务之最简洁,最易维护,最合理的做法,那就别怕它。


模板与泛型编程

条款41:了解隐式接口和编译期多态

面向对象编程世界总是以显示接口和运行期多态来解决问题。

Templates及泛型编程的世界,与面向对象有根本上的不同。在此世界中显示接口和运行期多态仍然存在,但重要性降低。反倒是隐式接口和编译期多态更为重要。

显示接口由函数的签名式(也就是函数名称,参数类型,返回类型)构成。隐式接口并不基于函数签名式,而是由有效表达式组成。

条款42:了解typename的双重意义

声明template参数时,前缀关键字class和typename可互换

请使用关键字typename标识嵌套从属类型名称;但不得在base class lists(基类列)或member initialization list(成员初值列)内以它作为base class修饰符。

条款43:学习处理模板化基类内的名称

条款45:运用成员函数模板接收所有兼容类型

请使用member function templates生成“可接受所有兼容类型”的函数

如果你声明member templates用于“泛化copy构造”或“泛化assignment操作”,你还需要声明正常的copy构造函数和copy assignment操作符。

template<class T>
class shared_ptr {
public:
  shared_ptr(shared_ptr const& r);    // copy 构造函数

  template<class Y>                   // 泛化copy构造函数
  shared_ptr(shared_ptr<Y> const& r);

  shared_ptr& operator=(shared_ptr const& r);   // copy assignment

  template<class Y>                             // 泛化copy assignment
  shared_ptr& operator=(shared_ptr<Y> const& r);
};

条款46:需要类型转换时请为模板定义非成员函数

见条款24。当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”

之所以要friend是因为如果你想访问一个template内的函数必须在其内部才可以使用。

条款48:认识template元编程

TMP有两个伟大的效力。第一,它让某些事情更容易。如果没有它,那些事情将是困难的,甚至不可能的。第二,由于template metaprograms执行于C++编译期,因此可将工作从运行期转移到编译期。不过编译时间变长了。

typeid

template<unsigned n>
struct Factorial {
  enum {value = n * Factorial<n-1>::value};
};

template<>
struct Factorial<0> {
  enum {value = 1};
};


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值