《Effective C++》笔记

条款1:视C++为一个语言联邦:C、Object-Oriented C++、Template C++、STL。

条款2:尽量以const,enum,inline替换#define。

  1. 对于单纯的常量,最好以const对象或enums替代#define
  2. 对于形似函数的宏,最好用inline函数替代#define

条款3:尽可能使用const。

  • 将某些东西声明为const可以帮助编译器侦测出错误的用法。const可被添加到任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
  • 当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。

条款4:确定对象使用前已被初始化。

  • 对内置类的对象进行手动初始化,因为C++不保证初始化它们。
  • 构造函数最好使用初始化列表初始化成员,而不要在构造函数内使用赋值操作。初始化列表列出的成员变量,其排列次序应该和它们在class中声明的次序相同。

条款5:了解C++默认编写并调用哪些函数。

  • 编译器可以默认为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数,取地址运算函数。
  • 当手工创建一个构造函数后,编译不会再创建default构造函数。内含引用或const成员,编译器不会创建copy assignment操作符。

条款6:若不想使用编译器自动生成的函数,应该明确拒绝。

  • 为取消编译器自动提供的技能,可将相应的成员函数声明为private并且不予实现。

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

  • 如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。当子类以父类指针或父类的引用的形式被析构时,子类对象的成分没有被销毁,故需要将父类的析构函数需要为virtual。
  • Classes的设计目的如果不是作为base classes使用,或不是为了具备多态性,就不应该声明virtual析构函数。

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

  • 析构函数绝对不要抛出异常,如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后结束程序。

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

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

Student& operator = ( const Student& rhs)
{	    ....
	return *this;
}

条款11:在operator=中处理“自我赋值”。

条款12:复制对象时不要忘了它的每一成分。

条款13:以对象管理资源。

  • 以对象管理资源的两个关键想法:

(1) 获得资源后立刻放进管理对象。资源获取时机便是初始化时机。

(2) 管理对象运用析构函数确保资源被释放。

条款14:在资源管理类中小心copying行为。

  • 复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。
  • 普遍而常见的RAII class copying行为是:抑制copying、采用引用计数法。

条款15、在资源管理类中提供对原始资源的访问。

  • APIs往往要求访问原始资源,所以每一个RAII class应该提供一个“取得其所管理资源”的方法。

条款16:成对使用new和delete时要采取相同形式。

  • 如果在new表达式中使用了[ ],则在对应delete中也必须使用[ ]。若在new表达式中没有使用[ ],则在delete表达式中也不能使用[ ]。

条款17:以独立语句将newed对象置入智能指针

  • 以独立语句将newed对象置入智能指针,否则,一旦有异常抛出,就可能造成难以察觉的内存泄漏。

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

条款19:设计class犹如设计type。

  • 设计一个class需要考虑一下:

1、新type的对象应如何被创建和销毁?

2、对象的初始化和对象的赋值该有什么样的差别?

3、新type对象如果被passed by value,意味着什么?copy构造函数用来定义一个type by value该如何实现?

4、什么是新type的合法值?

5、新的type需要配合某些继承图系吗?

6、新type需要什么样的转换?

7、什么样的操作符和函数对新type时合理的?

8、什么样的函数应该驳回?那些正式必须声明为private者。

9、谁该取用新type的成员?

10、什么时新type的“未声明接口”?

11、新type有多么一般化?

12、真的需要一个新type吗?

条款20:宁以pass-by-reference-to-const 替换 pass-by-value。

  • 尽量以pass-by-reference-to-const 替代 pass-by-value。前者比较高效,并可避免切割问题。
  • 以上规则并不适用于内置类型、以及STL的迭代器和函数对象。对它们而言,pass-by-value往往比较适当。

条款21:必须返回对象时,别妄想返回其reference。

  • 绝不要返回指向一个local stack对象的pointer或reference。

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

  • 切记将成员变量声明为private,可赋予客户访问数据的一致性、可细微划分访问控制、允许约束条件得以保证。

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

  • 以non-member,non-friend函数替换member函数,这样可以增加封装性、包裹弹性和机能扩充性。

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

条款25:考虑写出一个不抛异常的swap函数。

条款26:尽可能延后变量定义式的出现时间。

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

  • 如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast,如果有个设计需要转型动作,试着发展无需转型的替代设计。
  • 如果转型是必要的,试着将它隐藏于某个函数背后,客户随后可以调用该函数,而不需要将转型放进他们自己的代码内。
  • 宁可使用C++新式转型,不要使用旧式转型。前者容易辨识出来。

条款28:避免返回handles指向对象内部成分。

  • 避免返回handles(包括references、pointer、迭代器)指向对象内部。

条款29:为“异常安全”而努力是值得的。

  • 异常安全函数即使发生异常也不会泄漏资源或允许任何数据结构败坏。

条款30:透彻了解inline的里里外外。

  • 将大多数inline限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。
  • 不要只因为function templates出现在头文件,就将它们声明为inline。

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

  • 如果使用object references或object pointers可以完成任务,就不要使用objects。为声明式和定义式提供不同的头文件。

条款32:确定你的public继承塑模出is-a关系

  • “ public继承 ” 意味着is-a。适用于base classes身上的每一件事情一定适用于derived classes身上,因为每一个derived class对象也是一个base class对象。

条款33:避免遮掩继承而来的名称。

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

  • 可以为pure virtual函数提供定义(提供实现代码),但调用它的唯一途径是“调用时明确指出class名称”。
  • 接口继承和实现继承不同。在public继承之下,derived classes总是继承base class的接口。
  • pure virtual function(纯虚函数)只具体指定接口继承,impure virtual(非纯虚函数)具体指定接口继承及缺省实现继承。non-virtual函数具体指定接口继承以及强制实现继承。

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

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

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

  • 绝不重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数唯一应该覆写的东西却是动态绑定。

条款38:通过复合塑模出has-a。

  • 复合(composition)的意义和public继承完全不同。在应用域(application domain)复合意味has-a(有一个)。在现实域(implementation domain)复合意味is-impllemented-in-terms-of(根据某物实现出)。

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

条款40:明智而谨慎地使用多重继承。

  • virtual继承会增加大小、速度、初始化复杂度等等成本。如果virtual base classes不带任何数据,将是最具适用价值的情况。
  • 多重继承的确有正当用途。其中一个情节设计“public继承某个Interface class”和“private继承某个协助实现的class”的组合。

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

  • 对classes而言接口是显式的(explicit),以函数签名为中心。多态是通过virtual函数,发生在运行期。
  • 对template参数而言,接口是隐式的,基于有效表达式。多态则通过template具现化和函数重载,解析发生于编译阶段。

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

  • 声明template参数时,前缀关键字class和typename可互换。
               template<typename/class T>
  • 请使用关键字typename表示嵌套从属类型名称;但不得在base class lists(基类列表)或member initalization list(成员初始化列表)内以它作为base class修饰符。
	//嵌套从属类型名称示例:
	
	template<typename C>
	
	void print2nd(const C& container)
	{  
		if(container.size() >= 2)
		{
			typename C::const_iterator iter(container.begin());
			
	//在缺省的情况下C::const_iterator不被认为是类型,在嵌套从属关系C::const_iterator前加typename说明C::const_iterator为类型。
		}
	}
  • 一般性规则:任何时候想要在template中指涉一个嵌套从属类型名,就必须在紧邻它的前一个位置放上typename。
//不能使用typename示例
template<typename T>
class Derived::public Base<T>::Nested   // base class list中不允许typename
{
public:
explicit Derived(int x):Base<T>::Nested(x)  // member init list中不允许typename
{ 
	typename Base<T>::Nested temp; //嵌套从属名称,需要加typename;
}
};

//接手一个迭代器,为迭代器所指的对象做一份local副本temp;
template<typename IterT>
void workWithIterator(IterT iter)
{  
	//std::iterator_traits<IterT>::value_type   类型为ItetT对象所指之物的类型
	typename std::iterator_traits<IterT>::value_type temp(*iter) ;
}

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

条款44:将与参数无关的代码抽离templates。

  • Templates生产多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。
  • 因非类型模板而造成的代码膨胀,往往可以消除,做法是以函数参数或class成员变量替换template参数。

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

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

  • 当编写一个class template,而它所提供的与此template相关的函数支持所有参数之隐式类型转换时,将那些函数定义为class template内部的friend函数。
template<typename T>
class Rational{
public:  
	friend constRational operator * (constRational& lhs, constRational&rhs)  
	{    
		return Rational(lhs.numerator()*rhs.numerator(),lhs.denominator()*rhs.denominator());  
	} 
};

条款47:使用traits classes表现类型信息。

// 使用std::iterator_traits<IterT>::

template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{  
	doAdvance(iter, d, typename std::iterator_traits<IterT>::iterator_category());
}

条款48:认识template元编程。

  • Template metaprogramming(TMP,模板元编程)是编写template-based C++程序并执行于编译器的过程。
  • Template metaprogramming(TMP,模板元编程)可以将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率。

条款49:了解new-handler的行为。

  • 一个良好设计的new-handler函数必须做以下的事情:

(1)让更多内存可被使用
(2)安装另一个new-handler
(3)卸载new-handler
(4)抛出bad_alloc(或派生自bad_alloc)的异常
(5)不返回,通常调用abort()或exit()。

  • set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
  • nothrow new是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能抛出异常。

条款50:了解new和delete的合理替换时机。

  • 有许多理由需要写个定义的new和delete,包括改善效能、对heap运用错误进行调试、收集heap使用信息。
    原因:为了检测运用错误、收集动态分配内存的使用统计信息、增加分配和归还的速度、降低缺省内存管理器带来的空间额外开销、弥补缺省分配器中的非最佳齐位、将相关对象成簇集中、获得非传统的行为(比如自定义delete,在其中所归还内存内容覆盖为0,增加应用程序的数据安全性)。

条款51:编写new和delete时需要固守常态。

  • operator new应该内含一个无限循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler。它也应该有能力处理0 bytes申请。
  • operator delete应该在收到null指针时不做任何事。

条款52:写了placement new也要写placement delete。

  • operator new接受的参数除了一定会有的size_t外还有其它参数,这就是所谓的placement new。
void* operator new(std::size_t, void* pMemory) throw(); //标准库 #include <new>
  • 当写一个placement operator new,请确定也写出对应的placement operator delete。如果没有这样做,你的程序可能会发生隐蔽的而时断时续的内存泄漏。
  • 当声明placement new 和 placement delete,请确定不要无意识地遮掩它们的正常版本。

条款53:不要轻易忽略编译器的警告。

  • 严肃对待编译器发生的警告信息。
  • 不要过度依赖编译器的报警能力,因为不同的编译器对待事情的态度并不相同。一旦移植到另一个编译器上,你原本依赖的警告信息有可能消失。

条款54:让自己熟悉包括TR1在内的标准程序库。

条款55:让自己熟悉Boost。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值