问题背景
有时候会发现仅仅改动了某个类的一点实现(仅仅是几句代码),在编译时却发现要编译整个工程!特别是工程有点大时,编译要等很久很久。。。只为修改一个问题,时间都浪费在等待上了。为了避免这一问题,其实是可以通过巧妙的设计来避开文件间的依赖。
形成依赖的主要缘由:
头文件依赖,A类依赖B类,B类依赖C类,结果只改动C类的任何一个数据成员,A,B都得重编译。C++在编译期间,编译器需要知道对象的大小(才能够分配足够的内存空间),这就导致了上面的互相依赖的编译。
解决办法:
1.把类的实现细节隐藏在一个指针背后(指针的大小显然是已知的),指针指向实现细节的类,这样修改细节时不会引发大面积的依赖编译。
2.使用抽象基类。
解决方案一:指针背后的游戏
假设我们要实现一个Person的类
类的定义如下:
//Person.h文件
#pragma once
#include <iostream>
#include <string>
#include <memory>
//实现细节类的前置声明 注意:并没有用include引入该类的定义式,而是巧妙的用声明式替换它,这样头文件Preson对实现细节没有任何的依赖!
class PresonPri;
class Person
{
public:
Person();
~Person();
std::string adder()const;
std::string name()const;
std::string date()const;
private:
//这里使用智能指针,该指针用来指向类的实现细节
std::tr1::shared_ptr<PresonPri> pImpl;
};
在Person类的实现部分调用 他的细节类PresonPri来实现。
类的实现部分:
//Person.cpp 文件
#include "Person.h"
#include "PresonPri.h"
Person::Person()
:pImpl(new PresonPri())
{
}
Person::~Person()
{
}
std::string Person::adder() const
{
return pImpl->_adder();
}
std::string Person::name() const
{
return pImpl->_name();
}
std::string Person::date() const
{
return pImpl->_date();
}
细节类
#pragma once
#include <string>
class PresonPri
{
public:
PresonPri();
~PresonPri();
std::string _adder()const;
std::string _name()const;
std::string _date()const;
private:
std::string name;
std::string adder;
std::string date;
};
----------
#include "PresonPri.h"
PresonPri::PresonPri()
{
adder = "地球";
name = "xf";
date = "2020-1-12";
}
PresonPri::~PresonPri()
{
}
std::string PresonPri::_adder() const
{
return adder;
}
std::string PresonPri::_name() const
{
return name;
}
std::string PresonPri::_date() const
{
return date;
}
//main.cpp
#include "Person.h"
int main()
{
using namespace std;
Person per;
cout << per.adder() <<per.date() <<per.name() << endl;
getchar();
}
这样设计,任何引用 person.h的代码,在该类的实现改变时都不会被重新编译,毕竟不管你怎么该实现的细节,Person类的头文件始终没有变,又有什么理由重新编译浪费时间呢。
可以简单的看看文件依赖图
main.cpp仅仅依赖与 person.h
+——–
总结:这种分离的关键在于,用“声明的依存性”替换“定义的依存性”。其本质是:实现中让头文件尽可能的自我满足。
- 如果使用对象的引用或者指针可以完成任务,就不要使用对象。因为只靠一个类型声明式就可以定义指向该类型的引用或者指针;但如果要定义某种类型的对象,就必须要用到该类型的定义式了。
- 尽量用class的声明式替换class的定义式。注意:当你声明一个函数而用到一个class时,你并不需要该class的定义式,构造函数也不例外,因此可以这样改进之前的Person类:
//perfwd.h
#pragma once
class PresonPri;
class Data;
class Adder;
----------
//person.h
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include "perfwd.h"
class Person
{
public:
Person(Adder & adder,Data &data,std::string &name); //有参构造函数
~Person();
std::string adder()const;
std::string name()const;
std::string data()const;
private:
std::tr1::shared_ptr<PresonPri> pImpl;
};
----------
其他文件略......
----------
//main.cpp
#include "Person.h"
#include "Data.h"
#include "Adder.h"
int main()
{
Adder adder;
Data data;
std::string name;
Person per(adder,data,name);
std::cout << per.data() << std::endl;
getchar();
return 0;
}
解决方案二:使用抽象类
抽象类是描述接口的,通常是没有数据成员的,也没有构造函数,只有一个virtual析构函数以及一组接口函数
抽象类一般是不会改动的,客户也只能创建他的指针和引用。那么如何创建他的实例?可以使用一种工厂方法,返回他的实例指针。当然,该指针是指向他的具体类的。(父类的指针可以指向其子类)
贴一下代码:
//Person.h文件
#pragma once
#include <iostream>
#include <string>
#include <memory>
class Person
{
public:
~Person();
static std::tr1::shared_ptr<Person> creat();
virtual std::string adder()const=0;
virtual std::string name()const=0;
virtual std::string date()const=0;
};
----------
//Person.cpp文件
#include "Person.h"
#include "PresonPri.h"
Person::~Person()
{
}
std::tr1::shared_ptr<Person> Person::creat()
{
return std::tr1::shared_ptr<Person>(new PresonPri());
}
----------
//PresonPri.h文件
#pragma once
#include "Person.h"
#include <string>
class PresonPri:public Person
{
public:
PresonPri();
~PresonPri();
std::string adder()const;
std::string name()const;
std::string date()const;
private:
std::string m_name;
std::string m_adder;
std::string m_date;
};
----------
//PresonPri.cpp文件
#include "PresonPri.h"
PresonPri::PresonPri()
{
m_adder = "地球";
m_name = "xf";
m_date = "2020-1-12";
}
PresonPri::~PresonPri()
{
}
std::string PresonPri::adder() const
{
return m_adder;
}
std::string PresonPri::name() const
{
return m_name;
}
std::string PresonPri::date() const
{
return m_date;
}
----------
//main.cpp文件
#include "Person.h"
int main()
{
using namespace std;
std::tr1::shared_ptr<Person> per(Person::creat());
cout << per->adder() <<per->date() <<per->name() << endl;
getchar();
}
总结:这两种方法都是通过解除接口和实现之间的耦合关系,从而降低文件间的编译依存性。
我的个人网站
http://www.breeziness.cn/
我的CSDN
http://blog.csdn.net/qq_33775402
转载请注明出处 小风code www.breeziness.cn