-
搜了一下:不建议使用前置声明:如何看待C++前置声明? - 知乎,所以重点讨论pimpl这种操作。
- 如果修改了某个class的实现,而依赖这个class有很多个文件,于是可能会出现修改了一点点代码,但编译需要很长的时间。这就是文件之间的依赖太多导致的。
- 比如有这样一份代码:
#include <string> #include "date.h" #include "address.h" class Person { private: std::string theName; Date theBirthDate; Address theAddress; public: ... };
Class Person和其依赖的头文件就形成了一种编译依存关系,如果date.h和address.h有什么修改,那么所有包含此头文件的文件都会重新编译。
原因也很简单:如果有这样的声明:Person pp; 那么要定义pp,必须要知道Pesron的大小,而Pesron的大小需要 Class Person {...};来确定,所以改变了某个文件A,那么依赖A的所有相关文件都需要被重新编译。
- 那有什么办法可以降低依赖吗?
- 针对Person我们可以这样做:把Person分割为两个类,一个只提供接口(Person提供接口函数),另一个负责实现接口(PersonImpl实现Person接口的函数):这样
Person
的客户完全与Date
,Address
以及Person
的实现(PersonImpl)细目分离了。#include <string> #include <memory> class PersonImpl; class Date; //声明 class Person { public: ... private: std::shared_prt<PersonImpl>p; }
(1)首先是class Date;这种写法,相比于用#include "Date.h",区别是:用声明的依赖替换定义的依赖(声明与定义-参考:C++类中变量的初始化顺序_zion--6135的博客-CSDN博客)。这样如果#include "Date.h"有修改,对于Class Pesron{...};所在文件不需要重新编译。实现了编译上的隔离。Person类与Date类之间的修改互不影响,实现了真正的接口(class Date)与实现(Pesron看不到也不需要关心Date内变量构成)隔离。
- 通过pimpl实现接口与实现隔离
(2)Person与PersonImpl的隔离:std::shared_prt<PersonImpl>p; 这种设计叫做pimpl idiom(pointer to implementation指向实现的指针),将Pesrson类的函数实现都放到类PersonImpl里面去管理,这样一个对客户的类Person就完全将实现隐藏,客户连函数都看不到(如果PesronImpl类也不能看到,应该是可以的,因为class PeseonImpl隐藏了头文件)。具体例子如下:
//main.cpp #include <iostream> #include <string> #include <memory> #include "Person.h" int main() { Person pp; std::cout << pp.getname() << std::endl; return 0; } //Person.h #include "iostream" #include <memory> #include "PersonImpl.h" class Person { public: const std::string getname(){ std::cout << p->getname() << std::endl; return p->getname(); }; private: std::shared_ptr<PersonImpl> p = std::make_shared<PersonImpl>(); }; //PersonImpl.h #include "iostream" using namespace std; class PersonImpl { public: PersonImpl() { cout << "start" << endl; }; const string getname() { return name; }; ~PersonImpl() { cout << "finish" << endl; } private: string name = "Person TT"; int n2; };
main.cpp代表客户端调用,通过在Person类的接口函数,里面对PersonImpl的实现函数进行封装。这就是所谓的“接口与实现完全隔离”
- 通过virtual class 来定义接口, derived class来具体实现。实现接口与实现分离
(1)需要名字的接口person类:(通过pure virtual function来表明是接口)
(2)需要一个类PersonImpl来继承并实现这些接口
class Person { // interface class public: virtual const std::string getname() = 0; virtual ~Person(){}; }; class PersonImpl : public Person //derived class { public: virtual const string getname() { return name; }; PersonImpl(string nameTmp):name(nameTmp) { cout << "start" << endl; }; ~PersonImpl() { cout << "finish" << endl; } private: string name = "Person default"; };
(3)既然接口有了,实现有了,希望一个factory函数来获取一个PersonImpl对象,(为啥不直接new PersonImpl来使用?因为这是接口,统一流程,关于内存的管理,需要交给Person类来操作,不希望调用接口的人去管理)所以Class Person需要有如下函数create;
class Person { // interface class public: virtual const std::string getname() = 0; virtual ~Person(){}; static std::shared_ptr<Person> create(string name); };
(4)所以通过Person类实现create,以及调用,客户用的所有函数都在Person类里面,真正的实现在PersonImpl里面。
//person.h #include "iostream" #include <memory> // #include "PersonImpl.h" using namespace std; class Person { // interface class public: virtual const std::string getname() = 0; virtual ~Person(){}; static std::shared_ptr<Person> create(string name); private: // static std::shared_ptr<PersonImpl> pp; //管理内存 }; class PersonImpl : public Person //derived class { public: virtual const string getname() { return name; }; PersonImpl(string nameTmp):name(nameTmp) { cout << "start" << endl; }; ~PersonImpl() { cout << "finish" << endl; } private: string name = "Person default"; }; //person.cpp #include "Person.h" std::shared_ptr<Person> Person::create(string name) { static std::shared_ptr<Person>pp (new PersonImpl(name)); return pp; }
客户端调用main.cpp,完全没有PersonImpl的痕迹,客户啥都看不了!!!
#include <iostream> #include <string> #include <memory> #include "Person.h" int main() { static std::shared_ptr<Person> p1 = Person::create("lbw"); std::cout << p1->getname() << std::endl; return 0; }
运行结果:
start
lbw
finish