Pimpl习惯用法指的是pointer to implementation,即指涉到实现的指针。这种技巧就是把某类的数据成员用一个指涉到某实现类的指针替代,然后把原来在主类中的数据成员放置到实现类中,并通过指针间接访问这些数据成员。

示例代码:

//Widget.h中
class Widget {
public:
    Widget();

private:
    class Impl;                 //声明结构体或类
    std::unique_ptr<Impl> pImpl; //声明指涉到它的指针
};


//Widget.cpp中
#include "Widget.h"
#include "Impl.h"  //在实现文件内包含Impl的头文件

Widget::Widget():pImpl(std::make_unique<Impl>()){

}


//main.cpp
#include "Widget.h"
Widget w;   //调用Widget实例,但此时会编译报错

以上代码中在main.cpp中调用Widget实例会提示编译报错,原因是,Widget实例w在析构时,会用调用智能指针pImpl的默认析构函数,我们知道默认析构函数时inline型别的,所以相当于在Widget.h文件内调用Impl的析构函数,而在Widget.h文件内,Impl是非完整型别的(只有声明,没有定义,其定义是在Impl.h内),从而提示编译报错。

为了解决上述问题,我们只需要把Widget的析构函数定义放在Widget.cpp内即可,即自己实现Widget的析构函数,若默认析构函数的内容确实符合要求,只是为了移动其实现为止到Widget.cpp内,我们可以在Widget.cpp内将函数实现使用“=default”代替即可。

因为如果自定义析构函数,编译器将被阻止生成默认移动操作,所以如果需要类支持移动操作,就必须自定义,此时也必须将移动操作放在类的实现文件内。

//Widget.h中
class Widget {
public:
    Widget();
    ~Widget();  //声明析构函数
    Widget(const Widget&& w);  //声明移动构造函数
    Widget& operator=(const Widget&& w);  //声明移动赋值函数
private:
    class Impl;                 //声明结构体或类
    std::unique_ptr<Impl> pImpl; //声明指涉到它的指针
};


//Widget.cpp中
#include "Widget.h"
#include "Impl.h"  //在实现文件内包含Impl的头文件

Widget::Widget():pImpl(std::make_unique<Impl>()){

}

//在实现文件内使用默认析构及移动操作
Widget::~Widget() = default;
Widget::Widget(const Widget&& w) = default;
Widget& Widget::operator=(const Widget&& w)= default;


//main.cpp
#include "Widget.h"
Widget w;   //编译通过