Effective C++ 34

34.将文件间的编译依赖性降到最低。

对于一个大型程序,其结构是错综复杂的,当你对一个类进行一些改动时,修改的不是接口,而是类的实现,即只是一些细节部分,但重新生成程序时,所有用到这个类的的文件都要重新编译。这里题目指的是这个意思。但实际上,我在vs2012实践了一下,对于类B与类A相关联,类B的实现依赖于类A,若类A的实现发生了改变,并不会影响B,即生成时,编译器只会去重新编译A,而对于依赖于A的用户程序,并不会像其所说那样全部重新编译。好吧,我这里总算是明白其所说的修改其实现的意思了。

修改类的实现: 类的接口只是类中提供给外部的函数, 而类的实现是指类实现接口函数所需要的内部逻辑和数据结构,如一些私有的函数,以及一些私有的成员数据。修改这些类实现的,对于实现函数的修改就必须修改函数的声明,而数据成员的修改就是数据成员的类型以及数量的修改。当进行这些修改时,就必定会导致调用这个类的用户程序都要重新编译。

一般的解决方法是将实现与接口分开,即对于本来使用的一个类,将其转换为两个类,一个类为接口类,供用户程序调用,一个类为实现类,有具体的实现逻辑以及实现所需的数据成员,且接口类能够指向对应的实现类,对于实现逻辑的更改不会影响用户程序,因为用户程序只与接口类连接,而隐藏了实现逻辑修改造成的影响,只有当接口改变时,才需要重新编译。分离的关键是,对类定义的依赖 被 对类声明的依赖取代,降低编译依赖性,将 提供类定义 即#include 指令 的任务由原来的函数声明头文件转交给包含函数调用的用户文件。

即不在头文件中包含其他头文件,除非缺少它们就不能编译,而一个一个地声明所需要的类,让使用这个头文件的用户自己通过include去包含这些头文件,以使用户代码通过编译。

实现这种接口与实现分离,在c++中一般有两种方法,一种是 将一个对象的实现隐藏在指针的背后,即用一个指针指向某个不确定的实现。这样的类称为句柄类或信封类。而指向的实现类称为 主体类或者信件类。句柄类,即接口只是将所有函数调用转移到对应的主体类中,有主题类也就是实现类来真正完成工作。接口中要将原来的实现需要的数据成员转换为函数,而去调用实现类中的数据成员 来实现功能,即接口中使用函数来实现对实现类中的数据成员实现传递和返回。

假如简单实现两个类,A ,B,C,A中放一个int,b中放一个doubel,C中放两者之和,写出代码如下:

classA.h:

#pragma once
class ClassA{
public:
public:
	int a;
	ClassA(int x){a = x;}
	ClassA(){a = 0;}
	int getA() const{return a;};
};
ClassB.h:

class ClassB{
public:
	double b;
	double getB() const{return b;}
	ClassB(double x){b = x;}
	ClassB(){b = 0;}
};
ClassC.h,即接口:

//这个头文件就是所谓的接口,如此将接口与实现分离后,只有修改接口时,才会导致使用该接口的用户程序重新编译
class ClassA;//只声明,在接口中只要知道有这些类,而在实现中才去include这些头文件
class ClassB;
class ClassCImpl;

class ClassC{
public:
	ClassC(const ClassA& xa,const ClassB& xb);
	virtual ~ClassC();
	int getA() const;//函数来返回实现类中的数据成员
	double getB() const;
	double getC() const;
private:
	ClassCImpl *impl;//使用指针来指向实现类
	//int aaa;//在接口中任意进行修改,就要重新编译其与其用户程序
};
ClassC.cpp,接口的函数,调用 实现类中的函数进行返回。

//这里也是对于接口的实现,修改这里的数据,不会导致其他程序的重新编译
#include "ClassC.h"//这是接口ClassC的函数具体实现
#include "ClassCImpl.h"//要包含实现类的定义,且实现类中与ClassC中有一样的成员函数

ClassC::ClassC(const ClassA& xa,const ClassB& xb){
	impl = new ClassCImpl(xa,xb);		
}
ClassC::~ClassC(){
	delete impl;
}
int ClassC::getA() const{
	return impl->getA();		
}
double  ClassC::getB() const{
	return impl->getB();		
}
double  ClassC::getC() const{
	return impl->getC();		
}
ClassCImpl ,实现类的定义:

#include "ClassA.h"
#include "ClassB.h"

class ClassCImpl{
public:
	ClassCImpl(const ClassA& xa,const ClassB& xb);
	int getA() const;//函数实现接口中函数
	double getB() const;
	double getC() const;
private:
	ClassA A;
	ClassB B;
	ClassB C;
};
ClassCImpl.cpp,实现类的简单的操作:

#include "ClassCImpl.h"//要包含实现类的定义,且实现类中与ClassC中有一样的成员函数

ClassCImpl::ClassCImpl(const ClassA& xa,const ClassB& xb){
	A = xa;
	B = xb;
	C = (B.getB() + A.getA());
}
int ClassCImpl::getA() const{

	return A.getA();		
}
double  ClassCImpl::getB() const{
	return B.getB();		
}
double  ClassCImpl::getC() const{
	return C.getB();		
}
这样就实现了接口与实现的分离,在ClassC中定义接口,在接口固定的情况下,在接口实现类ClassCImpl中进行任意的修改,编译器都只会重新编译实现类,而不会全部重新编译。这是使用句柄类实现的接口与实现分离。

另外一种方法成为协议类,即是这个类成为特殊类型的抽象基类。协议类只是为派生类确定接口,它没有数据成员,没有构造函数,有一个虚析构函数,有一些纯虚函数,这些纯虚函数组成了接口。
协议类的用户通过一个类似构造函数的的函数来创建新的对象,而这个构造函数所在的类就是隐藏在后的派生类。这种函数一般称为工厂函数,返回一个指针,指向支持协议类接口的派生类的动态分配对象。这个工厂函数与协议类解密相连,所以一般将它声明为协议类的静态成员。若重新声明一个ClassD,完成之前的功能,,但是为一个协议类,有:

ClassD.h:

//这个为协议类
class ClassA;//只声明,在接口中只要知道有这些类,而在实现中才去include这些头文件
class ClassB;

class ClassD{
public:
	virtual ~ClassD(){}
	virtual int getA() const = 0;//函数来返回实现类中的数据成员
	virtual double getB() const = 0;
	virtual double getD() const = 0;
	static ClassD* makeClassD(const ClassA& xa,const ClassB& xb);//这里使用静态成员来返回
};
再写一个派生类来实现CLassD的功能,RealClassD.h:

#include "ClassA.h"
#include "ClassB.h"
#include "ClassD.h"

class RealClassD:public ClassD{
public:
	RealClassD(const ClassA& xa,const ClassB& xb):A(xa),B(xb),D(B.getB() + A.getA()){}
	virtual ~RealClassD(){}
	 int getA() const;
	 double getB() const ;
	 double getD() const;

private:
	ClassA A;
	ClassB B;
	ClassB D;
};
而在这个派生类定义中,顺带实现ClassD中的返回指针的makeClassD的函数。这里:先从协议类中继承接口规范,然后在实现中实现接口中的函数。

#include "RealClassD.h"


int RealClassD::getA() const{
	return A.getA();
}
double RealClassD::getB() const{
	return B.getB();
};
double RealClassD::getD() const{
	return D.getB();	
}
ClassD* ClassD::makeClassD(const ClassA& xa,const ClassB& xb){
	return new RealClassD(xa,xb);
}

而在需要使用的地方,如此调用这个函数来指向需要的接口:

	ClassD* dd = ClassD::makeClassD(a,b);
	cout<<dd->getD()<<endl;
dd的指针动态绑定到返回的派生类对象,而在派生类中修改其实现的成员只要重新编译派生类的cpp就行了。


句柄类和协议类分离了接口与实现,从而降低了文件间的依赖性,当一定程度上有时间和空间上的消耗。对于一个程序转变为产品时,要用具体的类来取代句柄类和协议类。


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
内容简介: 有人说C++程序员可以分成两类,读过Effective C++的和没读过的。世界顶级C++大师Scott Meyers成名之作的第三版的确当得起这样的评价。当您读过《Effective C++中文版(第3版改善程序与设计的55个具体做法)》后,就获得了迅速提升自己C++功力的一个契机。   在国际上,本书所引起的反响,波及整个计算机技术出版领域,余音至今未绝。几乎在所有C++书籍的推荐名单上,本书都会位于前三名。作者高超的技术把握力、独特的视角﹑诙谐轻松的写作风格﹑独具匠心的内容组织,都受到极大的推崇和仿效。这种奇特的现象,只能解释为人们对这本书衷心的赞美和推崇。   《Effective C++中文版(第3版改善程序与设计的55个具体做法)》不是读完一遍就可以束之高阁的快餐读物,也不是用以解决手边问题的参考手册,而是需要您去反复阅读体会的,C++是真正程序员的语言,背后有着精深的思想与无与伦比的表达能力,这使得它具有类似宗教般的魅力。希望这本书能够帮助您跨越C++的重重险阻,领略高处才有的壮美风光,做一个成功而快乐的C++程序员。 Effective C++中文版(第3版改善程序与设计的55个具体做法)》一共组织55个准则,每一条准则描述一个编写出更好的C++的方式。每一个条款的背后都有具体范例支撑。第三版有一半以上的篇幅是崭新内容,包括讨论资源管理和模板(templates)运用的两个新章。为反映出现代设计考虑,对第二版论题做了广泛的修订,包括异常(exceptions)、设计模式(design patterns)和多线程(multithreading)。   《Effective C++中文版(第3版改善程序与设计的55个具体做法)》的重要特征包括:   ·高效的 classes、functions、templates 和inheritance hierarchies(继承体系)方面的专家级指导。   ·崭新的 "TR1" 标准程序库功能应用,以及与既有标准程序库组件的比较。   ·洞察 C++和其他语言(例如Java、C#、C)之间的不同。此举有助于那些来自其他语言阵营的开发人员消化吸收 C++ 式的各种解法。 目录: 译序 中英简繁术语对照 目录 序言 致谢 导读 1.让自己习惯C++ 条款01:视C++为一个语言联邦 条款02:尽量以const,enum,inline替换#define 条款03:尽可能使用const 条款04:确定对象被使用前已先被初始化 2.构造/析构/赋值运算 条款05:了解C++默默编写并调用哪些函数 条款06:若不想使用编译器自动成生的函数,就该明确拒绝 条款07:为多态基类声明Virtual析构函数 条款08:别让异常逃离析构函数 条款09:绝不在构造和析构过程中调用Virtual函数 条款10:令Operator=返回一个referenceto this 条款11:在Operator=中处理“自我赋值” 条款12:复制对象时勿忘其每一个成分 3.资源管理 条款13:以对象管理资源 条款14:在资源管理类中小心Coping行为 条款15:在资源管理类中提供对原始资源的访问 条款16:成对使用new和delete对象置入智能指针 条款17:以独立语句将newed对象置入智能指针 4.设计与声明 条款18:让接口容易被正确使用,不易被误用 条款19:设计class犹如设计type 条款20:宁以pass-by-reference-to-const替换Pass-by-value 条款21:必须返回对象时,别妄想返回其reference 条款22: 将成员变量声明为private 条款23: 宁以non-member、non-friend替换member函数 条款24:若有所参数皆需类型转换,请为此采用non-member函数 条款25:考虑写出一个不抛异常的swap函数 5.实现 条款26:尽可能延后变量定义式的出现时间 条款27:尽量少做转型动作 条款28:避免返回handles指向对象内部成分 条款29:为“异常安全”而努力是值得的 条款30:透彻了解inlining的里里外外 条款31:将文件间的编译依存关系降至最低 6.继承与面向对象设计 条款32:确定你的public继承塑模出is-a关系 条款33:避免遮掩继承而来的名称 条款34:区分接口继承和实现继承 条款35:考虚virtual函数以外的其他选择 条款36:绝不重新定义继承而来的non-virtual函数 条款37:绝不重新定义继承而来的缺省参数值 条款38:通过复合塑模出has-a或“根据某物实现出” 条款39:明智而审慎地使用private继承 条款40:明智而审慎地使用private继承 7.模板与泛型编程 8.定制new和delete 9.杂项讨论 A 本书之外 B 新旧版条款对映 索引
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值