Pimpl 手法:把某类的数据成员用一个指涉到某实现类/结构提的指针代替,然后把原来的主类中的数据成员放到实现类中,并通过指针间接访问这些数据成员;
class Widget
{
public:
Widget();
private:
std::string name;
std::vector<double> data;
Gadget g1, g2, g3;
};
如果头文件gadget.h 的内容发生了改变,则Widget 的客户必须重新编译。在C++98 中改善手法如下:
class Widget
{
public:
Widget();
~Widget();
private:
struct Impl;
Impl *pImpl;
};
由于Widget 不再提及std::string ,std::vector ,gadget.h 这些类型的头文件,会使得编译速度提升;
一个已经声明但未定义的类型成为非完整类型。Widget::Impl 就是非完整类型;Pimpl 用法第一部分:声明一个指针类型的数据成员,指涉到一个非完整类型;第二部分是动态分配和回收持有从前在原始类里面的那些数据成员的对象,而分配和回收代码放在实现文件中;
#include "widget.h"
#include "gadget.h"
#include <string>
#include <vector>
struct Widget::Impl {
std::string name;
std::vector<double> data;
Gadget g1, g2, g3;
};
Widget::Widget() : pImpl(new Impl){}
Widget::~Widget(){delete Impl;}
上述实现把依赖从widget.h (对Widget 客户可见并由他们使用)转移到了Widget.cpp (只对实现者可用并被实现)中;
使用智能的版本如下:
class Widget
{
public:
Widget();
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
}
#include "widget.h"
#include "gadget.h"
#include <string>
#include <vector>
struct Widget::Impl {
std::string name;
std::vector<double> data;
Gadget g1, g2, g3;
};
Widget::Widget() : pImpl(std::make_unique<Impl>()){}
上述代码本身能通过编译,但是客户如下代码不能:
#include "widget.h"
Widget w;
该问题产生的原因是w 被析构时所生成的代码引起。在使用了std::unque_ptr 的类定义里,我们未声明析构函数,编译器为我们自动生成一个。默认析构器是在std::unique_ptr 内部使用delete 运算符来针对裸指针实施析构函数。然而,在实施delete 运算符之前,典型的实现会使用C++11 中的static_assert 去确保裸指针未指涉到非完整类型。
解决办法为:之需要保证在生产析构函数std::unque<Widget::Impl> 代码处,Widget::Impl 是个完整类型即可。只要类型的定义可以被看到,它就是完整的。因此。成功编译的关键在于让编译器看到Wideget 的析构函数的函数体(编译器将要生成代码来析构std::unique_ptr 类型数据成员之处)的位置在widget.cpp 内部的Widget::Impl 之后
class Widget
{
public:
Widget();
~Widget();
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
}
在widget.cpp 定义析构函数,位置在Widget::Impl 之后
#include "widget.h"
#include "gadget.h"
#include <string>
#include <vector>
struct Widget::Impl {
std::string name;
std::vector<double> data;
Gadget g1, g2, g3;
};
Widget::Widget() : pImpl(std::make_unique<Impl>()){}
Widget::~Widget() = default;
|