Effective C++ 条款30、31

条款30 透彻了解inlining的里里外外

Inline函数,看起来像函数,动作像函数,比宏好得多,可以调用它们又不需要蒙受函数调用所招致的额外开销。但是,就像世界没有免费的午餐一样,inline的使用也是有代价的。比如说,使用inline会使整个代码量增加,造成程序体积太大,也可能会导致额外的换页行为,降低指令高速缓存装置的击中率,以及伴随这些而来的效率损失。

1、inline只是对编译器的一个申请,不是强制命令。该申请可以隐喻提出,也可以明确提出。

隐喻方式是将函数定义于class定义式内:

class Person {
public:
	int age() const { return theAge; }//一个隐喻的inline申请:age被定义于class定义式内
private:
	int theAge;
};

明确声明inline函数,即在其定义式前加上关键字inline:

template<typename T>
inline const T& max(const T& a, const T& b) {//此模版使用inline表明,据此template具现出来的函数都应该inlined时,才将template声明为inline。
	return a < b ? b : a;
}

inlining在大多数C++程序中是编译期行为。

2、并不是inline申请,编译器就会允许,以下情况编译器是拒绝inlined

i、拒绝将太过复杂(例如带有循环或递归)的函数inlining;

ii、所有对virtual函数的调用(除非是最平淡无奇的)也都会使inlining落空(因为virtual意味“等待,直到运行期才确定调用哪个函数(动态绑定嘛,推迟到运行时才确定对象是谁)”,而inline意味“执行前,先将调用动作替换为被调用函数的本体”,此时编译器都不知道该调用哪个函数,所以就很难责备它拒绝将函数本体inlining,讲的非常有道理。);

iii、编译器通常不对“通过函数指针而进行的调用”实施inlining,对inline函数的调用有可能被inlined,也可能不被inlined,取决于该调用的实施方式;

inline void f(){...}//假设编译器有意愿inline“对f的调用”
void(*pf)() = f;//pf指向f
f();//此调用被inlined
pf();//此调用或许不被inlined,因为它通过函数指针达成

3、构造函数和析构函数往往是inlining的糟糕候选人。具体分析见书上P137-P138

还需要值得注意的是,程序设计者必须评估“将函数声明为inline”的冲击:inline函数无法随着程序库的升级而升级。一旦inline f函数被改变,则所有牵涉f函数的代码都要变。而non-inline函数只需要重新连接就好,如果程序库采取动态连接,升级版函数甚至可以不知不觉地被应用程序吸纳,多棒!

另外,大部分调试器面对inline函数都束手无策。因为我们无法在一个并不存在的函数内设立断点。于是许多编译器禁止在debug版本中进行inlining。

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

为了尽可能减少程序编译依存关系,可以将接口从实现中分离。

1、针对Person,我们这样做:把Person分割为两个classes,一个只提供接口,另一个负责实现该接口。如果负责实现的那个所谓implementation class取名为PersonImpl,Person将定义如下:

#include<string>  //标准程序库组件不该被前置声明
#include<memory>  //此乃为了shared_ptr而含入

class PersonImpl;	//Persion实现类的前置声明
class Date;	//Person接口用到的classe的前置声明
class Address;

class Person {
	Person(const string& name, const Date& birthday, const Address& addr);
	string name()const;
	string birthDate()const;
	string address() const;

private:
	shared_ptr<PersonImpl> pImpl;//指针,指向实现物(类),这般设计常被称为pimpl idiom,这种classes内的指针名称往往就是pImpl。
}

上述设计,Person的客户就完全与Date,Address以及Person的实现细目分离(太棒啦,原因见后面一句话)。后者所述的类的任何实现修改都不需要Person客户端重新编译(接口声明没有发生改变嘛)。

总结上述代码特点:

i、如果使用object references或object pointers可以完成任务,就不要使用objects。可以只靠一个类型声明式就定义出指向该类型的references和pointers。但如果定义某类型的objects,就需要用到该类型的定义式。这也就是如果是定义一个类对象,编译器必须在编译期间知道对象的大小,此时class定义式必须合法的列出实现细目;

ii、如果能够,尽量以class声明式替换class定义式

class Date;//class声明式
Date today();//OK——这里并不需要Date的定义式,因为不需要进行类内成员函数调用等
void clearAppointments(Date d);//道理同上

iii、为声明式和定义式提供不同的头文件(类似接口和实现的分离)。

需要两个头文件,一个用于声明式,一个用于定义式,文件之前必须保持一致性,如果有个声明式被改变了,两个文件都得改变。

#include"datefwd.h"
Date today();
void clearAppointments(Date d);
#include"Person.h"
#include"PersonImpl.h"

Person::Person(const string& name, const Date& birthday, const Address& addr):
	pImpl(new PersonImpl(name,birthday,addr)){}
string Person::name()const {
	return pImpl->name();
}

上述Person class往往被称为Handle classes。

另一个制作Handle class的办法是,令Person成为一种特殊的abstract base class(抽象基类),即Interface class。

2、Interface class

一个针对Person而写的Interface class如下:

class Person {//抽象基类
public:
	static shared_ptr<Person> create(const string& name, const Date& birthday, const Address& addr);
	virtual ~Person();
	virtual string name()const = 0;
	virtual string birthDate() const = 0;
	virtual string address()const = 0;
};

客户必须对Person进行派生出类再具现出实体。

class RealPerson :public Person {//派生类
public:
	RealPerson(const string& name, const Date& birthday, const Address& addr):
		theName(name),theBirthDate(birthday),theAddress(addr){}
	virtual ~RealPerson(){}
private:
	string theName;
	Date theBirthDate;
	Address theAddress;
};
shared_ptr<Person> Person::create(const string& name, const Date& birthday, const Address& addr) {
	return shared_ptr<Person>(new RealPerson(name, birthday, addr));
}

Handle classes和Interface classes解除了接口和实现之间的耦合关系,从而降低文件间的编译依存性。

 

以上内容均来自Scott Meyers大师所著Effective C++ version3,如有错误地方,欢迎指正!相互学习,促进!!

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值