网站制作五个界面,公司合法网站域名怎么注册,佳木斯做网站,定制型网站制作公司写在前面
Pimpl(Pointer to implementation#xff0c;又称作“编译防火墙”) 是一种减少代码依赖和编译时间的C编程技巧#xff0c;其基本思想是将一个外部可见类(visible class)的实现细节#xff08;一般是所有私有的非虚成员#xff09;放在一个单独的实现类(implemen…写在前面
Pimpl(Pointer to implementation又称作“编译防火墙”) 是一种减少代码依赖和编译时间的C编程技巧其基本思想是将一个外部可见类(visible class)的实现细节一般是所有私有的非虚成员放在一个单独的实现类(implementation class)中而在可见类中通过一个私有指针来间接访问该实现类。
下面通过一个简单示例说明为什么使用Pimpl、如何使用Pimpl。
类普通实现
这里创建一个简单的Fruit类实现如下
//Fruit.h
#pragma once
#include stringclass Fruit
{
public:Fruit();~Fruit();void display();void setPrice(double dbPrice);double getPrice() const;private:std::string m_sName;double m_dbPrice;
};
//Fruit.cpp
#include Fruit.h
#include iostreamFruit::Fruit() : m_sName(), m_dbPrice(0.0)
{std::cout Fruit::Fruit\n;
}Fruit::~Fruit()
{std::cout Fruit::~Fruit\n;
}void Fruit::display()
{std::cout Name: m_sName , Price: m_dbPrice std::endl;
}void Fruit::setPrice(double dbPrice)
{std::cout ruit::setPrice: dbPrice std::endl;m_dbPrice dbPrice;
}double Fruit::getPrice() const
{std::cout Fruit::getPrice\n;return m_dbPrice;
}在其他文件(例main函数)中引用类
//main.cpp
#include iostream
#include Fruit.hint main()
{Fruit fruit;fruit.setPrice(5.88);fruit.display();
}上面是常见的类定义及使用方式这里可以很明显的发现两个问题 ①头文件暴露了私有成员。当然对于内部开发这无关紧要但对于一些对外的模块开发如dll外部使用人员有可能通过对外的头文件中的私有成员推测内部实现这显然不是公司所乐意见到的。 ②接口和实现耦合存在严重编译依赖性。例上面示例只实现的price成员的对外接口若添加name成员的对外接口setName、getName), 所有引用Fruit.h头文件的源文件(Fruit.cpp, main.cpp)都需要重新编译在大型的项目中这会花费很多编译时间。
因此对于需要对外隐藏信息或想要减少编译依赖的需求可以Pimpl模式实现类。
Pimpl实现
在上面Fruit类的基础上调整
//Fruit.h
#pragma once
//事先声明
class FruitPrivate;
class Fruit
{
public:Fruit();~Fruit();void display();void setPrice(double dbPrice);double getPrice() const;//为避免后续对头文件进行修改可事先预留所有成员的对外接口void setName(const std::string sName);std::string getName() const;private://成员放至私有类//std::string m_sName;//double m_dbPrice;FruitPrivate* m_priFruit;};
//Fruit.cpp
#include Fruit.h
#include iostream
#include string/***********************************FruitPrivate*********************************************/
class FruitPrivate
{
public:FruitPrivate();~FruitPrivate();void display();void setPrice(double dbPrice);double getPrice() const;private:std::string m_sName;double m_dbPrice;};FruitPrivate::FruitPrivate() : m_sName(), m_dbPrice(0.0)
{std::cout FruitPrivate::FruitPrivate\n;
}FruitPrivate::~FruitPrivate()
{std::cout PruitPrivate::~FruitPrivate\n;
}void FruitPrivate::display()
{std::cout FruitPrivate::display--Name: m_sName , Price: m_dbPrice std::endl;
}void FruitPrivate::setPrice(double dbPrice)
{std::cout FruitPrivate::setPrice--price: dbPrice std::endl;
}double FruitPrivate::getPrice() const
{std::cout FruitPrivate::getPrice;return m_dbPrice;
}/***********************************end FruitPrivate*********************************************/Fruit::Fruit() : m_priFruit(new FruitPrivate)//m_sName(), m_dbPrice(0.0)
{std::cout Fruit::Fruit\n;
}Fruit::~Fruit()
{std::cout Fruit::~Fruit\n;if (m_priFruit ! nullptr){delete m_priFruit;}
}void Fruit::display()
{//std::cout Name: m_sName , Price: m_dbPrice std::endl;m_priFruit-display();
}void Fruit::setPrice(double dbPrice)
{//std::cout ruit::setPrice: dbPrice std::endl;//m_dbPrice dbPrice;m_priFruit-setPrice(dbPrice);
}double Fruit::getPrice() const
{//std::cout Fruit::getPrice\n;//return m_dbPrice;return m_priFruit-getPrice();
}//按需实现
void Fruit::setName(const std::string sName)
{}std::string Fruit::getName() const
{}在其他文件(上例main函数)中的引用不变。
可以看到上面调整后的头文件中不再对外展示私有成员取而代之的是私有类的指针原本的私有成员存放到私有类中以实现隐藏。
另外再添加name成员的对外接口头文件已预留只需重新编译Fruit.cpp即可极大程度地减少编译依赖。
优点
①信息隐藏。私有成员完全可以隐藏在共有接口之外尤其对于闭源API的设计尤其的适合。同时很多代码会应用平台依赖相关的宏控制这些琐碎的东西也完全可以隐藏在实现类当中给用户一个简洁明了的使用接口。 ②加速编译。这通常是用pImpl手法的最重要的收益称之为编译防火墙(compilation firewall)主要是阻断了类的接口和类的实现两者的编译依赖性。这样类用户不需要额外include不必要的头文件同时实现类的成员可以随意变更而公有类的使用者不需要重新编译。 ③二进制兼容性。通常对一个类的修改会影响到类的大小、对象的表示和布局等信息那么任何该类的用户都需要重新编译才行。而且即使更新的是外部不可访问的private部分虽然从访问性来说此时只有类成员和友元能否访问类的私有部分但是私有部分的修改也会影响到类使用者的行为这也迫使类的使用者需要重新编译。
而对于使用pImpl手法如果实现变更被限制在实现类中那公有类只持有一个实现类的指针所以实现做出重大变更的情况下pImpl也能够保证良好的二进制兼容性这是pImpl的精髓所在。
缺点
①在私有类中对公有类的访问需另外设计实现。相较于常规实现这显然会加大开发人员的时间成本不过在Qt中有提供Q指针和D指针以支持公有类和私有类的相互访问而无需另外实现。 ②Pimpl对拷贝操作比较敏感要么禁止拷贝操作要么就需要自定义拷贝操作。每个类都需要对自己的所有成员的拷贝、赋值等操作负责。在公有类中虽然只有一个私有类的指针成员但其私有类内部有多少成员在外人看来不得而知因此共有类和私有类都需担负起成员的拷贝、赋值等操作的责任。 ③编译器将不再能够捕获const方法中对成员变量的修改。因为私有成员变量已经从公有类脱离到了实现类当中了公有类的const只能保护指针值本身是否改变而不再能进一步保护其所指向的数据。例上面对外的get接口虽然在公有类中限制为const不能修改私有类成员指针指向但在调用的私有类对于接口的内部也有变动成员的可能上例中在私用类对外的get接口后有加const限制。
注意事项
pImpl最需要关注的就是共有类的复制语义因为实现类是以指针的方式作为共有类的一个成员而默认C生成的拷贝操作只会执行对象的浅拷贝这显然违背了pImpl的原本意图除非是真的想要底层共享一个实现对象。针对这个问题解决方式有 ①禁止复制操作 将所有的复制操作定义为private的或者继承 boost::noncopyable或者在新标准中将这些复制操作定义为delete ②显式定义复制语义创建新的实现类对象执行深拷贝。要么不定义拷贝、移动操作符要定义就需要将他们全部重新定义。
优化
使用指针指针管理私有类指针成员拷贝、赋值操作限制。
//Fruit.h
#pragma once
#include string
#include memoryclass FruitPrivate;class Fruit
{
public:Fruit();~Fruit();//拷贝、赋值操作处理Fruit(const Fruit) delete; //私有成员为指针禁止浅拷贝Fruit operator(const Fruit) delete; //禁止赋值操作//可实现移动拷贝Fruit(Fruit) default;Fruit operator(Fruit) default;void display();void setPrice(double dbPrice);double getPrice() const;//为避免后续对头文件进行修改可事先预留所有成员的对外接口void setName(const std::string sName);std::string getName() const;private://成员放至私有类//std::string m_sName;//double m_dbPrice;//FruitPrivate* m_priFruit;//使用智能指针管理私有类指针std::unique_ptrFruitPrivate m_priFruit;};
//Fruit.cpp
#include Fruit.h
#include iostream/***********************************FruitPrivate*********************************************/
class FruitPrivate
{
public:FruitPrivate();~FruitPrivate();//拷贝、赋值操作和公有类保持一致FruitPrivate(const FruitPrivate) delete;FruitPrivate operator(const FruitPrivate) delete;FruitPrivate(FruitPrivate) default;FruitPrivate operator(FruitPrivate) default;void display();void setPrice(double dbPrice);double getPrice() const;private:std::string m_sName;double m_dbPrice;};FruitPrivate::FruitPrivate() : m_sName(), m_dbPrice(0.0)
{std::cout FruitPrivate::FruitPrivate\n;
}FruitPrivate::~FruitPrivate()
{std::cout PruitPrivate::~FruitPrivate\n;
}void FruitPrivate::display()
{std::cout FruitPrivate::display--Name: m_sName , Price: m_dbPrice std::endl;
}void FruitPrivate::setPrice(double dbPrice)
{std::cout FruitPrivate::setPrice--price: dbPrice std::endl;
}double FruitPrivate::getPrice() const
{std::cout FruitPrivate::getPrice;return m_dbPrice;
}/***********************************end FruitPrivate*********************************************/Fruit::Fruit() : m_priFruit(std::make_uniqueFruitPrivate())//m_sName(), m_dbPrice(0.0)
{std::cout Fruit::Fruit\n;
}Fruit::~Fruit()
{std::cout Fruit::~Fruit\n;//if (m_priFruit ! nullptr)//{// delete m_priFruit;//}
}void Fruit::display()
{//std::cout Name: m_sName , Price: m_dbPrice std::endl;m_priFruit-display();
}void Fruit::setPrice(double dbPrice)
{//std::cout ruit::setPrice: dbPrice std::endl;//m_dbPrice dbPrice;m_priFruit-setPrice(dbPrice);
}double Fruit::getPrice() const
{//std::cout Fruit::getPrice\n;//return m_dbPrice;return m_priFruit-getPrice();
}//按需实现
void Fruit::setName(const std::string sName)
{}std::string Fruit::getName() const
{return ;
}总结
类的常规实现和Pimpl实现各有优劣。若只是为了快速开发且没有对外隐藏需求常规实现无疑是很好的选择若想要减少编译依赖且不想对外展示私有成员可选择使用Pimpl实现代价就是开发及维护成本的提高。