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继承接口规格,然后实现接口所覆盖的函数,第二个实习那法使用多重继承。

原创粉丝点击