effective c++学习笔记二
资源管理
条款13:以对象管理资源
为确保资源总是被释放,我们需要将资源放进对象内,当控制流离开时,改对象的析构函数总是会自动释放那些资源。
把资源放进对象内,便可以依赖析构函数的自动调用确保资源被释放
许多资源被动态分配与heap内而后被用于单一区块或者函数内。他们应该在控制流离开那个区域或者函数时被释放。
用auto_ptr:类指针对象 智能指针,其析构函数自动对其所指对象调用delete
std::auto_ptr<investment> pInv (createInvestment());
关键点:获得资源后立即放进管理对象,以对象管理资源的观念通常被称为“资源获取时机便是初始化时机”(RAII)。获得某个资源的时刻将他初始化给某个管理对象
管理对象运用析构函数确保资源被释放。一旦对象被销毁,不管以某种方式离开控制流,析构函数就被自动调用,资源被释放
不可以让多个auto_ptr指向一个对象,防止未定义的行为。
如果拷贝函数复制一个auto_ptr,复制所得的指针将获得资源的唯一使用权,原来那个变成null
STL要求正常的复制行为,因此都不可以使用auto_ptr
auto_ptr的替代方案是引用计数型智慧指针 RCSP
智能指针,持续追踪共有多少对象指向某笔资源,并在没人使用资源的时候删除该资源。行为类似于垃圾回收,但是无法打破环状引用。
TR1的tr1::shared_ptr就是一个RCSP 并且可以用在STL上
智能指针都在析构函数内做delete而不是delete[]。那就意味着动态分配而得的array身上不要使用上述两种智能指针。
一般都用STL的容器替代,或者用BOOST
条款14:在资源管理类中小心copying行为
关键问题:大概一个RAII对象被复制时 会发生什么事
资源的拷贝行为决定了RAII的拷贝行为
一般会有两种可能:
禁止复制。很多时候对RAII对象的复制并不合理。
对底层资源用引用计数法:复制时将该资源的引用计数递增
也可以选择:
复制底部资源。当我需要资源管理类的唯一理由是,当你不再需要某个复件时确保他被释放。在此时复制资源管理对象,应该同时也复制其所包覆的资源,进行深度拷贝。比如string
转移底部资源的所有权。auto_ptr
条款15:在资源管理类中提供对原始资源的访问
有时候需要一个资源对象的指针,而不能传递一个智能指针的对象给他
这时就需要一个函数可以将RAII类对象转换为其所含之原始资源,可以用显式转换和隐式转换
显式转换:tr1::shared_ptr和auto_ptr都提供一个get成员函数,用来执行显式转换,也就是他会返回智能指针内部的原始指针(的复件)
隐式转换:智能指针同样也重载了*和->操作符,允许转至底部原始指针。
一般来说显式转换更安全 隐式转换更方便
条款16:成对使用new和delete时使用相同的形式
delete的最大问题在于:在需要释放的内存中到底有多少个对象,这意味着多少个析构函数将会被调用。
释放数组时需要delete[]
尽量不要对数组形式做typedef动作,尽量用STL库来实现
条款17:以独立语句将newed对象置入智能指针
c++编译器执行语句的顺序有很大弹性。而编译器对跨越语句的各项操作没有重新排列的自由。
当资源被创建和资源被转换为资源管理对象之间可能有发生异常干扰,一旦异常被抛出,可能会出现难以察觉的资源泄露。
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
设计与声明
条款18:让接口容易被正确使用,不容易被误用
理想上,如果客户使用某个接口而结果不能获得他预期的行为,这个代码就不应编译通过。
欲开发一个理想的代码,必须知道你的客户会产生什么样的错误
许多客户端错误可以因为导入新型类而获得预防。可以导入一些外覆类型,对传入参数加以限制
限制类内什么事情可做什么不可做,常见的就是加const
一个一般性的准则:除非有好的理由,否则就应该让你的自定义类型的行为尽量和内置类型一致
让工厂函数直接返回一个智能指针,可以防止大多数的资源泄露问题,消除了客户的资源管理责任
条款19:设计class犹如设计type
了解你所面对的问题,获得最佳的答案
1.新对象如何被创建和销毁:构造函数和析构函数及内存分配及释放函数。
2.对象的初始化和对象的赋值该有什么样的差别:构造函数和赋值函数的差异
3.新的对象如果被值传递,意味着什么:决定拷贝构造函数的实现
4.什么是新的类型的合法值:对成员变量而言,只是有些值是有效的,决定了类的必须维护的约束条件,决定了成员函数的必须的错误检查工作。
5.新的类型需要配合继承吗:如果需要继承某些现有的类,那么就受到那些类的设计的束缚,特别是受到成员函数是否是虚函数的影响。
6.你的新类型需要什么样的转换:是否需要进行显示或者隐式转换
7.什么样的操作符对此新类型是合理的:需要申明哪些函数,其中某些应该是成员函数
8.什么样的标准函数应该被驳回:(编译器自动生成的函数)声明为private
9.谁该取用新的类型的成员:帮助解决函数的访问权限(public,private。。。。)
10.什么是新类型的未声明的接口,他对效率异常安全性以及资源运用提供何种保证。
11.你的新类型有多么一般化:如果并非定义一个类而是一整个类家族,那么就应该定义一个新的类模板
条款20:以pass by reference to const替换pass by value
以值传递的副本会调用拷贝构造函数,代价很大,原参数不会受到任何改动,因为有改动都是在副本上进行
用const& para效率更高,而且必须用const,保证在函数内没有被改动
以传引用的方式传递参数也可以避免对象切割的问题发生。当一个继承类的对象以值传递的方式传递并被视为一个基类对象,基类对象的拷贝构造函数就被调用,仅留下一个基类的对象,子类衍生的特性都被消除。
而传引用就不会发生这样的问题,传入的是什么对象就是什么对象
在编译器底层,引用传递经常是以指针实现出来,因此如果是内置类型,值传递效率更高。
STL中基本都是用值传递实现,迭代器和函数对象的实践者有责任观察他们是否高效且不受切割问题的影响。
可以假设“值传递并不昂贵”的唯一对象就是内置类型和STL的迭代器和函数对象。
条款21:必须返回对象时,别妄想返回其reference
一个函数内的本地对象可能在离开该控制流时被销毁,返回该对象的引用会引起未定义的行为
同样也不要返回一个引用指向一个heap-allocated 对象,也不要返回一个指针或引用指向一个local static对象而有可能同时需要多个这样的对象。
条款22:将成员变量声明为private
如果成员都不是public,那么访问成员变量的唯一方法就是通过函数,可以让你对成员变量的处理有更加精准的控制,如只读,只写等
也可以获得更好的封装,将变化控制在函数内,可以为所有可能的实现提供弹性
protected的封装性并不比public强,因为当一个变量被移除时,其所有派生类都会被破坏,是一个不可知的巨大的量。
条款23:宁以non-member,non-friend替换member函数
面向对象要求数据尽可能地被封装,封装的东西越多,越少的代码可以看到数据,我们改变数据的能力也就越强,可以使能够改变事物而只影响到有限的客户。
如果一个成员函数和一个non-member,non-friend函数之间选择,并且两者提供一样的功能,那么non-member,non-friend函数的封装性更强,因为他不能访问private成员。
注意:为了封装而使函数成为类的non-member,non-friend函数并不意味他不可以是另一个类的成员函数
条款24:若所有参数都需类型转换,请为此采用non-member函数
只有当参数位于参数咧内,这个参数才是隐式类型转换的合格参与者,地位相当于被调用之成员函数所隶属的那个对象,即this对象的那个隐喻参数,不是隐式转换的合格参与者。
条款25:考虑写出一个不抛出异常的swap函数
STL中的swap将两个对象的值彼此赋予对方,只要类型支持拷贝,缺省的swap就可以正常工作
但是有时候只需要将两个指针互换就可以完成交换,拷贝反而很慢。
方法就是为swap提供一个成员函数,并且保证这个函数不会抛出异常
如果提供一个成员swap,也应该提供一个非成员swap 来调用前者,对于类而非模板 也要特化std:swap
调用swap时应对std::swap使用using声明式,然后调用swap并不带任何名称空间修饰
为用户定义类型进行std template全特化是好的,但是不要尝试在std内加入对std来说是全新的东西。
浙公网安备 33010602011771号