effective c++读书笔记(五)
来源:互联网 发布:godaddy域名促销码 编辑:程序博客网 时间:2024/04/28 00:34
实现
太快定义变量可能造成效率上的拖延;过度使用转型(casts)可能导致代码变慢有难以维护,又招来微妙难解的错误;返回对象“内部数据的handlers”可能会破环封装并留给客户dangling handles;未考虑异常带来的冲击则可能导致资源泄漏和数据破坏;过度热性的inline可能导致代码膨胀;过度耦合则可能导致让人不满意的冗长build times。
尽可能延后变量定义式的出现时间
std::string encryptPassword(const std::string& password){ using namespace std; string encrypted; if(password.length() < MinimuPasswordLenth){ throw logic_error("Password is too short"); } //... return encrypted;}
上述代码如果提前抛出了异常就会导致在encrypted上浪费了构造函数与析构函数的时间,应该当需要这个对象的时候在定义它。
std::string encryptPassword(const std::string& password){ using namespace std; if(password.length() < MinimuPasswordLenth){ throw logic_error("Password is too short"); } string encrypted; //... return encrypted;}
上述代码仍然不够好,因为他调用的是默认构造函数,然后在对对象赋值,它的效率比直接在构造时指定初值的效率差。
std::string encryptPassword(const std::string& password){ //.... std::string encrypted(password); encrypt(encrypted); return encrypted;}
上述的方法是最好的做法,但是当出现需要在循环内部使用对象的时候,一般将对象的定义放在循环体外,更加高效。
Widget w;for(int i = 0; i < N; i++){ w = ; //....}for(int i = 0; i < N; i++){ Widget w(); //...}
第一个版本的成本是:1个构造函数+1个析构函数+n个赋值操作
第二个版本的成本是:n个构造函数+n个析构函数
尽量少做转型动作
(T) expression; T(expression); // c风格的转型动作
const_cast(expression) : 去除对象的常量性
dynamic_cast(expression): 安全向下转型,用来决定某对象是否归属继承体系中的某个类型
reinterpret_cast(expression): 意图执行低级转型,实际动作取决于编译器,不可移植。
static_cast(expression): 强迫隐式转换,但是无法作用于将const转为non-const对象。
建议使用c++风格转型:容易辨识, 各转型动作的目标愈窄化,编译器俞可能诊断出错误;
转型其实什么都没做,只是告诉编译器把某种类型视为另一种类型,这是错误的观念;
class Base{};class Derived: public Base{};Derived d;Base *pb = &d; //在运行时有个偏移量施行于Derived*指针上。用以取得正确的Base* 指针值。//这段代码表明,某一对象可能拥有一个以上的地址,因此应该避免做出对象在c++中如何布局的假设。更不应该依次假设为基础执行任何转型动作。
1.如果可以,尽量避免转型,特别是在注重效率的代码中避免使用dynamic_cast。如果有个设计需要转换类型,试着发展无需转型的替代设计。
class Window{public: virtual void onResize(){ //....}};class SpecialWindow: public Window{public: virtual void onResize(){ static_cast<Window>(*this).onResize(); //这是不可行的方法 }};//调用的是稍早转型动作所建立的一个*this对象的base class成份的暂时副本身上的onResize().//并非对当前对象身上调用window::onResize()之后又在该对象身上执行SpecialWindow专属动作//它是在当前对象上的base class成份的副本上调用Window::onResize,然后在当前对象身上执行specialWindow专属动作。
解决方法:第一,使用容器并在其中存储直接指向derived class对象的指针,消除通过base class接口处理对象的需要
typedef std::vector<std::shared_ptr<SpecialWindow> > VPSW;VPSW winPtrs;//...for(VPSW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter) (*iter)->blink();
第二种方式,通过base class接口处理所有可能的各种window派生类,那就是在base class内提供virtual函数做你想对各个window派生类做的事
class Window{public: virtual void blink(){}};class SpecialWindow: public Window{public: virtual void blink(){}};typedef std::vector<std::shared_ptr<Window>> VPW;VPW winPtrs;for(VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter) (*iter)->blink();
上述两种方法不是放之四海而皆准,但是许多情况下他们都提供一个可行的dynamic_cast替代方案
2.如果转型是必要的,试着将它隐藏在某个函数背后。客户随后可以调用该函数,不需要将转型放进自己的代码内
3. 宁可使用c++style转型,不要使用旧式转型。应该避免使用所谓的“连串(cascading dynamic_cast)”。
避免返回handles指向对象内部成份
class Point{public: Point(int x, int y); void setX(int newVal); void setY(int newVal);};struct RectData{ Point ulhc; Point lrhc;};class Rectangle{public: Point& upperLeft()const{ return pData->ulhc;} Point& lowerRight()const{ return pData->lrhc;}private: std::shared_ptr<RectData> pData;};Point coor1(0,0);Point coor2(100,100);const Rectangle rec(coor1, coor2);rec.upperLeft().setX(50); //false 导致rec的值被修改
1:成员变量的封装性最多只等于“返回其reference”的函数访问级别。 虽然ulhc和lrhc都被声明为private,他们实际上是public,因为public函数upperLeft和lowerRight传出了它们的reference。
2:如果const成员函数传出一个reference,后者所指数据与对象自身有关联,而它又被存储与对象之外,那么这个函数的调用者可以修改那笔数据,这正是bitwise constness的一个附带结果。
以上的讨论也使用与指针,迭代器,他们都是handles,返回一个代表对象内部数据的handles,随之而来的是降低对象封装性
绝对不要让成员函数返回一个指针指向“访问级别较低”的成员函数,因为后者的实际访问级别就会提高如同前者(访问级别较高的);
//解决方案是加上一个constclass Rectangle{public: const Point& upperLeft() const{ return pData->ulhc;}};//这些函数只是让渡读取权,涂写权任是禁止的//还是会导致一些问题,dangling handles
dangling handles所指的东西不复存在,这种不存在的对象,最长将的来源就是函数的返回值。
class GUIObject{ //....};const Rectangle boundingBox(const GUIObject& obj); //by value方式返回一个rectangleGUIObject *pgo;const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft());//导致dangling handles
upperLeft作用于一个临时的temp对象,boundingBox执行完毕后,temp被析构,导致pUpperLeft指向一个不存在的对象。
不建议成员函数返回handle,不代表不可以这样使用(string和vector里的operator[]函数就是这样使用的),但是毕竟是少数
为异常安全而努力是值得的
异常安全的条件: 1不泄露任何资源; 2 不允许数据败坏。
class PrettyMenu{public: void changeBackground(std::istream& imgSrc);private: Mutex mutex; Image* bgImage; int imageChanges};void PrettyMenu::changeBackground(std::istream& imgSrc){ lock(&mutex); delete bgImage; ++imageChanges; bgImage = new Image(imgSrc); //抛出异常时,资源泄漏,数据被破坏 unlock(&mutex);}//解决方法void PrettyMenu::changeBackground(std::istream& imgSrc){ Lock m1(&mutex); //将互斥器交给资源管理类 delete bgImage; ++imageChanges; bgImage = new Image(imgSrc); //抛出异常时,资源泄漏,数据被破坏}
异常安全函数提供以下三个保证:
1基本承诺:异常抛出时,程序内的所有事物仍然是保持在有效状态下,没有任何对象和数据结构因此破坏,所有对象处于一种内部前后一致的状态。
2强烈保证:异常抛出时,程序状态不改变。函数如果成功就是成功,失败了,就会到函数调用前的状态。
3不抛掷保证:承诺不抛出异常,因为它们总是能够完成它们原先的承诺。内置类型所有操作都提供nothrow保证。
只要满足以上三个中的一个就可以说它是异常安全性的。
//一个较好的实现,但是仍然不是强烈保证的异常安全性class PrettyMenu{public: std::shared_ptr<Image> bgImage;};void PrettyMenu::changeBackground(std::istream& imgSrc){ Lock m1(&mutex); bgImage.reset(new Image(imgSrc)); // Image 构造函数抛出异常 ++imageChanges; }
一个设计策略可以导致强烈的保证:copy and swap:为打算修改的对象做出一个副本,然后在副本上进行所有必要的修改,若修改抛出异常,原对象仍保持未改变状态。待所有改变成功后,将修改过的那个副本和原件在一个不抛出异常的操作中swap。这个策略是针对“全无或全有”改变来说是一个很好的办法。
struct PMImp{ std::shared_ptr<Image> bgImage; int imageChanges;};class PrettyMenu{private: Mutex mutex; std::shared_ptr<PMImp> pImp;};void PrettyMenu::changeBackground(std::istream& imgSrc){ using std::swap; Lock m1(&mutex); std::shared_ptr<PMImp> pNew(new PMImp(*pImp)); pNew->bgImage.reset(new Image(imgSrc)); ++pNew->imageChanges; swap(pImp, pNew);}
只要函数中调用的对象有一个不是异常安全的,那么这个函数整体就不是异常安全性的,整个程序就不是异常安全性的。
透彻了解inlining的里里外外
inline只是对编译器的一个申请,不是强制命令,可以隐喻,也可以明确指出。inline一般放在头文件内,因为编译环境在编译过程中进行inlining。virtual是不能申明为inline的,因为virtual是运行时指定调用哪个函数的,而inline是执行前将调用动作替换被调用函数的本体。一个函数是否是inline的,取决与编译器。编译器通常不对通过函数指针而进行的调用实施inlining。
将大多数inlining函数限制在小型,被频繁调用的函数身上。使用inline时要仔细考虑可能带来的后果。
将文件间的编译依存关系降至最低()
关键是以声明的依存性替换定义的依存性。
1.如果使用object reference或objec pointers可以完成任务,就不要使用objects。因为前两者只靠一个类型声明式就可以定义出指向该类型的reference和pointers,如果定义某类型的objects,就需要用到该类的定义式。需要知道具体占用内存的大小。
2.如果能够,尽量以class 声明式替换class定义式。
class Date; //class declarationDate today();void clearAppointments(Date d); //Date的definition//需要的时候,Date的定义才会曝光
3.为声明式和定义式提供不同的头文件
Handle classes:使用pointer to implementation技术的class就是handle class。
#include"Person.h" //包含class的定义式#include"PersonImp.h" Person::Person(const std::string& name, const Date& birthday, const Address& addr): pImp(new PersonImp(name, birthday, addr)){}std::string Person::name() const{ return pImp->name();}//让person变成一个handle class并不会改变它做的事,只会改变它做事的方法。
Interface class:另外一个制作handle class 的方法,就是令person成为一种特殊的abstract class。只描述derived class的接口。通常不带成员变量,没有构造函数,只有一个virtual析构函数以及一组pure virtual函数。
class Person{public: virtual ~Person(); virtual std::string name() const = 0; virtual std:string birthDate() const = 0; virtual std::string address() const = 0;};//客户必须通过pointers或reference来写程序,因为该class不能具现化//interface class的客户必须有办法为这种class创建对象。通常通过调用一个特殊的函数,这个函数扮演这“真正具现化的工作”,一般称为factory函数。这样的函数往往被声明为static
class Person{public: //... static std::shared_ptr<Person> create(const std::string &name,const Date& birthday, const Address& addr);};std::string name;Date dateOfBirth;Address address;std::shared_ptr<Person> pp(Person::create(name, dateOfBirth,address));std::cout<<pp->name()<<pp->birthday()<<pp->address();//真正的构造函数必须被调用。
另外假设interface class Person有一个具象的derived class RealPerson,后者提供继承而来的virtual函数的实现:
class RealPerson: public Person{public: RealPerson(const std::string &name,const Date& birthday, const Address& addr): theName(name),theBirthDate(birthday),theAddress(addr){} virtual ~RealPerson() {} std::string name() const; std::string birthday() const; std::string address() const;private: std::string theName; Date theBirthDate; Address theAddress;};//std::shared_ptr<Person> create(const std::string &name,const Date& birthday, const Address& addr){ return std::shared_ptr<Person>(new RealPerson(name,birthday,addr));}
RealPerson实现interface class的两个常见机制之一:从interface class继承接口规格,然后实现接口所覆盖的函数,第二个实习那法使用多重继承。
- Effective Java读书笔记五
- effective java读书笔记五
- Effective java 读书笔记( 五 )
- Effective C++读书笔记 五
- Effective C++ 读书笔记五
- effective c++读书笔记(五)
- 《Effective C++》读书笔记
- 《Effective C++》读书笔记
- 《Effective c++》读书笔记
- 《more effective c++》读书笔记
- <<effective c++>> 读书笔记
- 《Effective C++》读书笔记
- 《Effective C++》读书笔记
- Effective C++(1)读书笔记
- Effective C++(2)读书笔记
- 《Effective C++》读书笔记
- 《Effective C++》读书笔记
- 《effective c++》读书笔记【一】
- 数据库配置文件
- Vue——计算属性
- BS架构和CS架构的比对
- PAT 1007. 素数对猜想 (20)
- 遇到问题:push的时候出现fatal: Authentication failed for 'https://git.oschina.net/andthink/zsxw_android.git/'问
- effective c++读书笔记(五)
- 一个用原生js实现的小游戏---FlappyBird
- div的hover放大css,实现小图变大图
- SD卡启动详解
- linux github安装 Pytorch
- Redhat搭建本地Yum源
- 【LeetCode】算法题 27 Remove Element
- Ubuntu中配置apache的虚拟机时报apache2.serviceJob for apache2.service failed because the control process exite
- flex布局在ios8上的兼容性问题