Effective C++之五:实现
来源:互联网 发布:swiper.jquery.min.js 编辑:程序博客网 时间:2024/04/29 06:58
条款26 尽可能延后变量定义式的出现时间
(1)最好是定义时候赋初值
尽可能延后真实意义:不只应该延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义直到能够给他初值实参为止。
如果是这样,不仅能便面构造(析构)非必要对象,还可以避免无意义的默认构造行为。
(2)
//A:1构造 1析构 n copy
Widget w;
for (int i =0; i < n; i++)
{
w = 取决于i的某个值;
...
}
//B:n构造 n析构
for (int i =0; i < n; i++)
{
Widget w(取决于i的某个值);
...
}
除非(1)赋值成本比“构造+析构”低 (2)你正在处理代码中效率高度敏感的部分。 否则应该选B。
条款27 尽量少做转型动作
(0)
如果可以尽量避免转型,特别是在注重效率的代码中避免dynamic_casts。如果有个设计需要转型动作,试着发展无需转型的替代设计。
如果转型是必须的,试着将它隐藏与某个函数背后。客户随后可以调用该函数,而不虚将转型放进他自己的代码。
宁可使用c++(新式)转型,不要使用老式( (T)expression )。前者很容易辨认出来,而且也比较有着分门别类的执掌。
新式转型学习:
(1)const_cast<T>(expreesion)
将对象的常量性移除, const -> non-const
(2)dynamic_cast<T>(expreesion)
执行安全向下转型(base->derived),决定某个对象是否归属继承体系中的某个类型。
通常你想在一个认定为派生类对象上执行派生类操作函数,但手上只有指向基类的指针或引用,只能靠他们来处理对象。有两个一般性做法可以解决:
第一:使用容器并在其中存储直接指向派生类对象的指针(智能指针)。
class Window{...};
class SpecialWindow: public Window{
public:
void blink();
...
};
typedef vector<shared_ptr<SpecialWindow>> VPSW; //直接存的是派生类的指针!
VPSW winPtrs;
...
for (auto it = winPtrs.begin(); it != winPtrs.end(); it++)
(*it)->blink();
第二:在基类提供virtual函数做你想对各个Window派生类做的事。
class Window{
public:
virtualvoid blink(){};
...
};
class SpecialWindow: public Window{
public:
virtualvoid blink();
...
};
typedef vector<shared_ptr<Window>> VPSW;
VPSW winPtrs; //内含指针,指向所有可能的Window类型
...
for (auto it = winPtrs.begin(); it != winPtrs.end(); it++)
(*it)->blink(); //动态绑定,多态
一定避免连串的if dynamic_cast转型判断!
(3)reinterpret_cast<T>(expreesion)
执行低级转型,例如pointer to int -> int
(4)static_cast<T>(expreesion)
强迫隐式转型,non-const -> const, int -> double, pointer-to-base -> pointer-to-derived。
int x =3, y = 2;
double d =static_cast<double>(x) / y;
class SpecialWindow: public Window{
public:
virtualvoid onresize()
{
static_cast<Window>(*this).onresize(); //*this转型为Window类型,然后调用其onresize函数,是非常错误的!
... //这其实是在当前对象之base class成分的副本上调用Window::onresize(),并不能修改对象的内容,改动的是副本
}
...
};
class SpecialWindow: public Window{
public:
virtualvoid onresize()
{
Window::onresize(); //正确用法!
...
}
...
};
条款28 避免返回handles(号码牌,用来取得某个对象)指向对象内部成分
避免返回handles(包括reference,指针,迭代器)指向 对象内部。
遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生"虚吊号码牌"的可能性将至最低。
(1)成员函数返回引用,后者的访问级别就会提高如前者
class Point{
public:
Point(int x,int y);
...
void setX(int x);
void setY(int y);
...
};
struct RectData{ //两个点表示一个矩形
Point ulhc;
Point lrhc;
};
class Rectangle{
public:
...
const Point& upperLeft()const //这一句必须返回const,不然返回的引用就能被修改了
{return pData->ulhc;}
const Point& lowerRight()const
{return pData->lrhc;}
private:
shared_ptr<RectData> pData;
};
int main()
{
Point coord1(0,0);
Point coord2(100,100);
constRectangle rec(coord1, coord2);
rec.upperLeft().setX(50); //报错!因为upperLeft()返回值不让修改
return0;
}
(2)成员函数返回引用,可能导致引用所指的东西不复存在
class GUIObject{...};
const Rectangle boundingBox(constGUIObject& obj); //返回 by-value。如果不存下来,就会被销毁
GUIObject* pgo;
...
const Point* pupperleft = &(boundindBox(*pgo).upperLeft());//取得一个指针指向外框左上点
//其实是tmp.upperLeft(),这个语句之后,tmp被销毁,Point指针无处可指
条款29 为“异常安全”而努力是值得的
(0)
异常安全性的函数:
1、不泄露任何资源
2、不允许数据破坏
异常安全的函数提供以下三种承诺之一:
1.基本保证,异常被抛出,程序内的任何事物都在有效的状态下。
2.强烈保证,如果异常被抛出,程序状态不改变。如果函数中失败,程序恢复到调用函数之前状态。往往能够用copy and swap手法。
3.承诺决不抛出异常:一定成功。
class PrettyMenu{
public:
...
void changeBackground(istream& imgSrc); //改变背景图像
...
private:
Metux mutex; //互斥器
Image* bgImage; //目前的背景图像
int imageChanges; //被改变次数
};
void PrettyMenu::changeBackground(istream& imgSrc)
{
lock(&mutex);
delete bgImage;
++imageChanges;
bgImage = new Image(imgSrc); //这步导致异常,unlock就不会执行,互斥器就锁住了,泄露了资源。
//而bgImage指向一个已删除对象,这就破坏了数据。
unlock(&mutex);
}
(2)
在我们有能力提供以上保证的时候,我们尽可能的提供最高的异常保证,但是当我们的代码调用了过去的传统的代码,那我们无法提供任何保证。
在保证异常安全性方面,我们最常用的手段就是以对象来管理资源,以及copy and swap手法。
对于以对象管理资源对来说,我们主要的目的是,在代码抛出异常的时候,资源离开其作用域时,自动释放其资源,比如说互斥锁(这很重要,应为不这样的话我们是很容易造成死锁的),以资源管理对象的重要手段就是智能指针。
解决资源泄露 是 使用对象管理资源-资源管理类。
void PrettyMenu::changeBackground(istream& imgSrc)
{
Lock ml(&mutex); //条款14
delete bgImage;
++imageChanges;
bgImage = new Image(imgSrc);
}
class PrettyMenu{
...
private:
shared_ptr<Image> bgImage; //用于资源管理的智能指针
...
};
void PrettyMenu::changeBackground(istream& imgSrc)
{
Lock ml(&mutex); //条款14
bgImage.reset(new Image(imgSrc));//以new Image(imgSrc)的结果设置bgImage内部指针。
++imageChanges;
}
copy and swap手法是我们提供强烈保证的重要手段,对对象状态做出“全有或全无”改变的一个很好的办法:
先在副本上做出修改,待所有改变都成功后,将副本和对象在不抛出异常的操作中置换(swap)。
struct PMImpl{
shared_ptr<Image> bgImage;
int imageChanges;
};
class PrettyMenu{
...
private:
Mutex mutex;
shared_ptr<PMImpl> pImpl;
};
void PrettyMenu::changeBackground(istream& imgSrc)
{
usingstd::swap; //条款25
Lock ml(&mutex); //条款14
shared_ptr<PMImpl> pNew(newPMImpl(*pImpl)); //生成副本
pNew->bgImage.reset(new Image(imgSrc));
++pNew->imageChanges;
swap(pImpl, pNew); //置换(swap)数据,释放mutex
}
函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。
条款30 透彻了解inlining的里里外外
(0)incline函数背后的整体观念是:对此函数每个调用,用函数本体替换之。
(1)隐喻的inline是将函数定义于class定义式内。注:friend函数也可被定义于class内,如果真是那样,也是隐喻声明为inline。
class Person{
public:
...
int age()const {return theage;} //一个隐喻的inline。age被定义于class定义式内。
private:
int theage;
};
(2)明确声明的inline:template和inline无关,不要只因为function templates出现在头文件,就将它声明为inline。
template<typename T>
inline const T&std::max(const T& a,const T& b)
{
return a < b ? b : a;
}
大多数inline限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。
(3)inline是个申请,编译器可以忽略。大部分编译器拒绝将太过复杂的函数inline,而对virtual的inline肯定会落空。因为:
virtual意味着“等待,直到运行期才确定调用哪个函数”,而inline意味执行前,先将调用动作替换为调用函数本体。
(4)如果编译器无法给函数inline,会给出警告。
(5)通过函数指针进行的调用,通常编译器也许不会进行inline
inline void f(){}
void (*pf)() = f; //pf指向f
...
f(); //会被inlined
pf(); //或许不会被inlined,因为是通过函数指针达成
(6)实际上构造函数和析构函数往往是inline糟糕候选人。
class Base{
public:
...
private:
string bm1, bm2;
};
class Derived:publicBase{
public:
Derived(){} //构造函数是空的??所以看起来是inline的绝佳候选人?
...
private:
string dm1, dm2, dm3;
};
下面是实际上 编译器为 Derived构造函数所产生的代码:
Derived::Derived()
{
Base::Base();
try{dm1.string::string();}
catch(...){
Base::~Base();
throw;
}
try{dm2.string::string();}
catch(...){
dm1.string::~string();
Base::~Base();
throw;
}
try{dm3.string::string();}
catch(...){
dm2.string::~string();
dm1.string::~string();
Base::~Base();
throw;
}
}
所以说并不适合inline。
(7)大部分调试器面对inline函数都束手无策。
(8)写不写为inline的策略:
一开始不要将任何函数声明为inline,或至少将inline范围局限在那些“一定成为inline”或“十分平淡无奇,比如age() ”的函数身上。
条款31 将文件的编译依存关系降至最低
(1) pointer to implementation
如果使用object reference或object pointers 可以完成任务,就不要使用objects。
你可以只靠一个类型声明式定义出指向该类型的reference和pointers。但如果定义某类型的objects,就需要用到该类型的定义式。
//Person的客户就完全与Dates,Addresses以及PersonImpl的实现细节完全分离。
class PersonImpl; //person实现类的前置声明
class Date; //person接口用到的classes的前置声明
class Address;
class Person{
public:
Person(conststring& name, constDate& birthday, constAddress& addr);
string name()const;
string birthDate()const;
string address()const;
...
private:
shared_ptr<PersonImpl> pImpl; //指向实现物
};
(2)
如果能够,尽量以class声明式替换class定义式。当用到某个class,并不需要它的定义式,pass-by-value也是。
在函数调用的时候再include它的Date头文件!
class Date; //class声明式
Date today(); //没问题,不需要Date的定义式
void clearAppointments(Date d); //同上
正式使用是:为声明式和定义式提供不同的头文件
include "datefwd.h" //这个头文件内声明(但未定义)Date类
Date today(); //没问题,不需要Date的定义式
void clearAppointments(Date d); //同上
支持“编译依存性最小化”的一般构思是:相依于声明式,不要相依于定义式。
基于此构思的两个手段是Handle classes 和Interface classes。
(3)像person这样的使用pimpl idiom的classes,往往被称为Handle classes。
#include "Person.h" //正在实现Person类,肯定要有class定义式
#include "PersonImpl.h"//也要有PersonImpl的定义式,否则无法调用其成员函数
person::person(const string& name,const Date& birthday, const Address& addr):pImpl(new PersonImpl(name, birthday, addr)){}
string Person::name()const
{
return pImpl->name();
}
(4)Interface classes
class Person{//抽象基类
public:
virtual ~Person();
virtual string name()const = 0;
virtual string birthDate()const = 0;
virtual string address()const = 0;
...
static shared_ptr<Person> create(const string& name, //返回智能指针-条款18 静态成员函数没法访问非静态成员函数
const Date& birthday,
const Address& addr);
};
class RealPerson: publicPerson{
public:
RealPerson(conststring& name,
constDate& birthday,
constAddress& addr)
:thename(name), theBirthDate(birthday), theAddress(addr){}
virtual ~RealPerson();
string name()const; //这三个函数实现略
string birthDate()const;
string address()const;
private:
string thename;
Date theBirthDate;
Address addr;
};
shared_ptr<Person>Person::create(conststring& name,
constDate& birthday,
constAddress& addr)
{
return shared_ptr<Person>(new RealPerson(name, birthday, addr));
}
(5)
程序库头文件应该以“完全且仅有声明式”的形式存在。这种做法不论是否涉及templates都适用。
- Effective C++(五)实现
- Effective C++(五)实现
- Effective C++(五)接口实现
- 细读《Effective C++》之五
- 细读《Effective C++》之五
- 细读《Effective C++》之五
- Effective C++之五:实现
- Effective C++(五)
- 《Effective C++》(五)
- Effective C++读书笔记之五
- effective C++: 5实现
- 《Effective C++》实现 章节
- Effective c++(笔记)----类与函数之实现
- Effective C++之实现
- effective c++ 之实现
- 《Effective C++》学习笔记(五)
- 《Effective C++》重点摘要(五)
- 《effective c++》学习笔记(五)
- 炒鸡酷,IT互联网程序员就业新前景:看极客是怎么靠两个披萨影响世界
- python flask几分钟实现web服务
- php 实现批量的下载pdf (使用filedownload)
- 一般IT部门职能有两种模式:管办分离模式和管办合一模式
- mysql 查询当天、本周,本月,上一个月的数据...
- Effective C++之五:实现
- byte转换至16进制字符串_计算机原/反/补码
- HDFS block丢失过多进入安全模式(safe mode)的解决方法
- python调度框架APScheduler基本使用
- 阻塞queue系列之LinkedTransferQueue
- 传参带事务的存储过程
- JAVA默认的get,set方法一个小坑
- Gradle学习(四)——Gradle守护进程
- shell编程排坑之if