当前位置:网站首页>API设计笔记:pimpl技巧
API设计笔记:pimpl技巧
2022-08-01 04:20:00 【拾牙慧者】
pimpl
pointer to implementation:指向实现的指针,使用该技巧可以避免在头文件暴露私有细节,可以促进API接口和实现保持完全分离。

Pimpl可以将类的数据成员定义为指向某个已经声明过的类型的指针,这里的类型仅仅作为名字引入,并没有完整地定义,因此我们可以将该类型的定义隐藏在.cpp中,这被称为不透明指针。
下面是一个自动定时器的API,会在被销毁时打印其生存时间。
原有api
// autotimer.h
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/time.h>
#endif
#include <string>
class AutoTimer
{
public:
// 使用易于理解的名字创建新定时器
explicit AutoTimer(const std::string& name); // xplicit避免隐式构造, 只能通过显示(explicit)构造.
// 在销毁时定时器报告生存时间
~AutoTimer();
private:
// 返回对象已经存在了多久
double GetElapsed() const;
std::string mName;
#ifdef _WIN32
DWORD mStartTime;
#else
struct timeval mStartTime;
#endif
};
这个API的设计包含如下几个缺点:
1、包含了与平台相关的定义
2、暴露了定时器在不同平台上存储的底层细节
3、将私有成员声明在公有头文件中。(这是C++要求的)
设计者真正的目的是将所有的私有成员隐藏在.cpp文件中,这样我们可以使用Pimpl惯用法了。
将所有的私有成员放置在一个实现类中,这个类在头文件中前置声明,在.cpp中定义,下面是效果:
autotimer.h
// autotimer.h
#include <string.h>
class AutoTimer {
public:
explicit AutoTimer(const std::string& name);
~AutoTimer();
private:
class Impl;
Impl* mImpl;
};
构造函数需要分配AutoTimer::Impl类型变量并在析构函数中销毁。
所有私有成员必须通过mImpl指针访问。
autotimer.cpp
// autotimer.cpp
#include "autotimer.h"
#include <iostream>
#if _WIN32
#include <windows.h>
#else
#include <sys/time.h>
#endif
class AutoTimer::Impl
{
public:
double GetElapsed() const
{
#ifdef _WIN32
return (GetTickCount() - mStartTime) / 1e3;
#else
struct timeval end_time;
gettimeofday(&end_time, NULL);
double t1 = mStartTime.tv_usec / 1e6 + mStartTime.tv_sec;
double t2 = end_time.tv_usec / 1e6 + end_time.tv_sec;
return t2 - t1;
#endif
}
std::string mName;
#ifdef _WIN32
DWORD mStartTime;
#else
struct timeval mStartTime;
#endif
};
AutoTimer::AutoTimer(const std::string& name) : mImpl(new AutoTimer::Impl())
{
mImpl->mName = name;
#ifdef _WIN32
mImpl->mStartTime = GetTickCount();
#else
gettimeofday(&mImpl->mStartTime, NULL);
#endif
}
AutoTimer::~AutoTimer()
{
std::cout << mImpl->mName << ":took" << mImpl->GetElapsed()
<< " secs" << std::endl;
delete mImpl;
mImpl = NULL;
}
Impl的定义包含了暴露在原有头文件中的所有私有方法和变量。
AutoTimer的构造函数分配了一个新的AutoTimer::Impl对象并初始化其成员,而析构函数负责销毁该对象。
Impl类为AutoTimer的私有内嵌类,如果想让.cpp文件中的其他类或者自由函数访问Impl的话可以将其声明为共有的类。
// autotimer.h
#include <string.h>
class AutoTimer {
public:
explicit AutoTimer(const std::string& name);
~AutoTimer();
class Impl;
private:
Impl* mImpl;
};
如何规划Impl类中的逻辑?
一般将所有的私有成员和私有方法放置在Impl类中,可以避免再公有头文件中声明私有方法。
注意事项:
不能在Impl类中隐藏私有虚函数,虚函数必须出现在公有类中,从而保证任何派生类都能重写他们
pimpl的复制语义
在c++中,如果没有给类显式定义复制构造函数和赋值操作符,C++编译器默认会创建,但是这种默认的函数只能执行对象的浅复制,这不利于类中有指针成员的类。
如果客户复制了对象,则两个对象指针将指向同一个Impl对象,两个对象可能在析构函数中尝试删除同一个对象两次从而导致崩溃。
下面提供了两个解决思路:
1、禁止复制类,可以将对象声明为不可复制
2、显式定义复制语义
#include <string>
class AutoTimer
{
public:
explicit AutoTimer(const std::string& name);
~AutoTimer();
private:
AutoTimer(const AutoTimer&);
const AutoTimer &operator=(const AutoTimer&);
class Impl;
Impl* mImpl;
}
智能指针优化Pimpl
借助智能指针优化对象删除,这里采用共享指针or作用域指针。由于作用域指针定义为不可复制的,所以直接使用它还可以省掉声明私有复制构造和操作符的代码。
#include <string>
class AutoTimer
{
public:
explicit AutoTimer(const std::string& name);
~AutoTimer();
private:
class Impl;
boost::scoped_ptr<Impl> mImpl;
// 如果使用shared_ptr就需要自己编写复制构造和操作符
}
Pimpl优缺点总结
优点:
1、信息隐藏
2、降低耦合
3、加速编译:实现文件移入.cpp降低了api的引用层次,直接影响编译时间
4、二进制兼容性:任何对于成员变量的修改对于Pimpl对象指针的大小总是不变
5、惰性分配:mImpl类可以在需要时再构造
缺点:
1、增加了Impl类的分配和释放,可能会引入性能冲突。
2、访问所有私有成员都需要在外部套一层mImpl→,这会使得代码变得复杂。
边栏推荐
- Error using ts-node
- [Search topic] After reading the inevitable BFS solution to the shortest path problem
- button remove black frame
- Game Theory (Depu) and Sun Tzu's Art of War (42/100)
- 故乡的素描画
- Input input box cursor automatically jumps to the last bug after the previous input
- Mysql基础篇(Mysql数据类型)
- win10 fixed local IP
- Elastic Stack的介绍
- What is a programming language
猜你喜欢

Article summary: the basic model of VPN and business types

Introduction to Oracle

Simulation of Active anti-islanding-AFD Active Anti-islanding Model Based on Simulink
![[Search topic] After reading the inevitable BFS solution to the shortest path problem](/img/f8/fbe906106dc8f7f7a6bd54d2dc3bc1.png)
[Search topic] After reading the inevitable BFS solution to the shortest path problem

Unknown Bounded Array

怀念故乡的面条

Make your Lottie support word wrapping in text fields

黑客到底可以厉害到什么程度?

Hackers can how bad to what degree?

基于ProXmoX VE的虚拟化家庭服务器(篇一)—ProXmoX VE 安装及基础配置
随机推荐
The Principle Of Percona Toolkit Nibble Algorithm
safari浏览器怎么导入书签
Flutter "Hello world" program code
Software Testing Weekly (Issue 82): In fact, all those who are entangled in making choices already have the answer in their hearts, and consultation is just to get the choice that they prefer.
高数 | 【重积分】线面积分880例题
MySQL3
解决ffmpeg使用screen-capture-recorder录屏,有屏幕缩放的情况下录不全的问题
Game Theory (Depu) and Sun Tzu's Art of War (42/100)
项目风险管理必备内容总结
【无标题】
Valentine's Day Romantic 3D Photo Wall [with source code]
基于Arduino制作非接触式测温仪
故乡的素描画
PMP 相关方管理必背总结
What is a programming language
Hackers can how bad to what degree?
Take you to experience a type programming practice
MySQL3
Flink 1.13 (8) CDC
罗技鼠标体验记录