pImpl设计如何将文件编译关系降低
Posted milaiko
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了pImpl设计如何将文件编译关系降低相关的知识,希望对你有一定的参考价值。
将文件间的编译关系降至最低
其中一个头文件发生改变,其他的都得重新编译。
但你的C++程序中的某个class实现文件做了轻微的修改,(这里的修改指的不是class接口,而是实现,而且只改private成分。因为只有一个class修改,当时输入make后发现这个世界都重新编译和链接了,问题出在哪里?
class Person
public:
Person(const std:string &name, const Date& birthday,
const Address& addr);
std::string name() const;
std::string birthdate() const;
std::string address() const;
private:
std::string theName; //实现细目
Date theBirthDate; //实现细目
Address theAddress; //实现细目
;
这个代码无法通过编译,因为编译器没有取得其实现代码所用到的classes string,Date和Address的定义式。 这种定义式通常通过#include指示符提供
#include<string>
#include"date.h"
#include"address.h"
但是这么一来Person定义文件和其含入文件之间形成了一种编译依存关系。如果这些头文件中有一个发生了改变,那么每一个含入Person class的文件接得重新编译,任何使用Person class的文件也必须重新编译。这连串的编译会给项目带来灾难。
尝试解决问题一 不把class的实现细目置于class定义式中
namespace std
class string;
class Date;
class Address;
class Person
public:
Person(const std:string& name, const Date& birthday, const Address& addr);
std::stirng name() const;
std::stirng bitrhdate() const;
std::stirng address() const;
;
这个想法有两个问题
- string不是一个class, 它是个typedef(定义为basic_string<char>)。所以针对string而做的前置声明并不正确;正确的声明比较复杂,而且标准头文件不太可能会成为编译瓶颈,所以应该仅仅使用适当的#includes完成目的。
- 编译器必须在编译期间知道对象的大小,那它如何知道一个Person对象有多大。获得这项信息的唯一方法就是询问class定义式。然而如果class定义式可以合法地不列出实现细目,编译器该如何知道分配空间。
尝试解决问题二 将Person分割 为两个classes
这些问题在java等语言不存在,因为那种语言定义对象时, 编译器只分配足够空间给一个指针(用以指向该对象)使用。
也就是它们将上述代码视同为这个样子。
int main()
int x;
Person* p;
也就是将对象实现细目隐藏在一个指针背后。
正对Person我们可以将Person分割成两个classes, 一个只提供接口,一个负责实现该接口。如果负责实现的那个所谓implementation class 取名为PersonImpl。
// Person.h
#include<string>
#include<memory>
class Date;
class Address;
class PersonImpl;
class Person
public:
Person(const std:string& name, const Date& birthday, const Address& addr);
std::stirng name() const;
std::stirng bitrhdate() const;
std::stirng address() const;
private:
std::shared_ptr<PersonImpl> pImpl;
;
这样的设计下,Person的客户就完全与Dates, Addresses以及Persons的实现细目分离了。哪些classes的任何实现修改都不需要Person客户端重新编译。 这就是真正的“接口与实现分离”
这时候有人疑惑,那么pImpl这个实现类到底是怎么实现的?以及pImpl设计是如何做到隐藏private对象的?
为了更好地理解,我们简化例子
// A.h
#ifndef A_H
#define A_H
#include<memory>
class A
public:
A();
~A();
void dosomething();
private:
class A_Impl;
std::shared_ptr<A_Impl> pImpl;
;
#endif
// A.cpp
#include<stdio.h>
#include"A.h"
class A::Impl
public:
int m_count;
Impl();
~Impl();
void doPrivatesomething();
;
A::Impl::Impl():m_count(0)
A::Impl::~Impl()
void A::Impl::doPrivatesomething()
printf("count = %d\\n", ++m_count);
A::A():pImpl(new Impl)
A::~A()
void A::dosomething()
pImpl->doPrivatesomething();
在private成员里面只有一个pImpl, 其他private成员变量都被这个指针封装起来,那么当A的用户(也就是使用#include"A.h"的用户),其中A的成员变量发生改变(如新增了private成员, 或者dosomething成员函数需要发生修改,但是接口类(A.h)都不会发生改变,那么在使用了#include"A.h"的代码文件就不需要重新编译,需要重新编译的只有A.cpp。
以上是关于pImpl设计如何将文件编译关系降低的主要内容,如果未能解决你的问题,请参考以下文章