C++中的pimpl惯用法
这篇给大家介绍什么是PImpl惯用法,以及使用std::unique_ptr 实现,并且实现了该的复制和赋值构造函数
pimpl 惯用法
在很多C++ 的API源码里,我们经常看到接口类通常指包含公有方法,而真正的实现类通常用一个不透明指针 (opaque pointer) 指向。例如
// x.h
class X{
public:
void func();
private:
class XImpl;
Ximpl *impl_;
};
// x.cpp
class X::XImpl{
// :::
// implement details here
};
这种方式称为 pimpl惯用法(pimpl idiom)。有时也称为编译防火墙(compliation firewalls)。
好处(为什么使用这种技巧)
编译防火墙。C/C++ 里有个特点——只要类的定义变了(即使是私有成员),所有include 该类定义的头文件的cpp文件都要被重新编译,这将导致大型C++项目编译时间过长。Pimpl隐藏了私有成员,在修改 XImpl实现时不需要重新编译客户代码;
隐藏实现细节,接口与实现分离。因为 .h 往往是暴露给用户的,好的API设计往往向用户隐藏实现细节。这样实现的修改、优化与接口分离,更加稳定健壮。
如何实现
把暴露给用户的类称为接口类X,实现类称为 XImpl, 有以下几个要点,
举个例子:
// in header file
class widget {
public:
widget();
~widget();
private:
class impl;
unique_ptr pimpl;
};
// in implementation file
class widget::impl {
//
};
widget::widget() : pimpl{ new impl{ /*...*/ } } { }
widget::~widget() { } // or =default
需要自己定义 widget的析构函数并且放到 impl 的定义之后,哪怕和default一样:
unique_ptr’s destructor requires a complete type in order to invoke delete (unlike shared_ptr which captures more information when it’s constructed). By writing it yourself in the implementation file, you force it to be defined in a place where impl is already defined, and this successfully prevents the compiler from trying to automatically generate the destructor on demand in the caller’s code where impl is not defined.
翻译过来就是: unique_ptr
实例
// x.h
#include // std::unique_ptr
#include
class X
{
public:
X();
~X();
X(const X& x);
X& operator=(const X& x);
void push_element(const std::string& str);
void print();
private:
class XImpl;
std::unique_ptr pimpl_;
};
// x.cpp
#include "x.h"
#include
#include
#include
class X::XImpl{
public:
XImpl() = default;
std::vector array_;
};
X::X()
:pimpl_{new XImpl}
{
}
// or =default; This is neccessary,because
// unique_ptr’s destructor requires a complete type in order to invoke delete;
// here force it to be defined in a place where impl is already defined
// see: https://herbsutter.com/gotw/_100/
X::~X(){}
X::X(const X &x)
{
pimpl_ = std::make_unique();
*pimpl_ = *x.pimpl_;
}
X &X::operator=(const X &other)
{
if(&other == this){
return *this;
}
*pimpl_ = *other.pimpl_;
return *this;
}
void X::push_element(const std::string &str)
{
pimpl_->array_.push_back(str);
}
void X::print()
{
for(const auto & i :pimpl_->array_){
std::cout<< i <<" ";
}
std::cout<
实例类X,可以向里面push 字符串,然后print()方法会打印出所有字符串。主要演示了Pimpl惯用法的实现,特别要注意需要在 x.cpp 里定义X析构函数,并且要放在XImpl类的定义之后,否则编译器会报错“allocation of incomplete type, type XImpl is incomplete”。
另外因为 是不可拷贝的,所以默认情况下 X也是不可拷贝的,为了支持copy语义,手动实现了copy 构造函数和copy-assignment operator。