当前位置:网站首页>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→,这会使得代码变得复杂。
边栏推荐
- What is dynamic programming and what is the knapsack problem
- Input输入框光标在前输入后自动跳到最后面的bug
- 雪糕和轮胎
- Article summary: the basic model of VPN and business types
- 情人节浪漫3D照片墙【附源码】
- Weekly Summary (*67): Why not dare to express an opinion
- FFmpeg 搭建本地屏幕录制环境
- 7月编程排行榜来啦!这次有何新变化?
- win10 fixed local IP
- Open source project site must-have & communication area function
猜你喜欢

数组问题之《下一个排列》、《旋转图像》以及二分查找之《搜索二维矩阵》

Interview Blitz 69: Is TCP Reliable?Why?

数组问题之《两数之和》以及《三数之和 》

每周小结(*67):为什么不敢发表观点

How to promote new products online?

The 16th day of the special assault version of the sword offer

【kali-信息收集】枚举——DNS枚举:DNSenum、fierce

Summary of mobile page optimization in seconds

干货!如何使用仪表构造SRv6-TE性能测试环境

产品经理访谈 | 第五代验证码的创新与背景
随机推荐
Passive anti-islanding-UVP/OVP and UFP/OFP passive anti-islanding model simulation based on simulink
PMP工具与技术总结
leetcode6132. Make all elements in an array equal to zero (simple, weekly)
This map drawing tool is amazing, I recommend it~~
The bigger and bigger the project is, I split it like this
Mysql基础篇(Mysql数据类型)
[Message Notification] How about using the official account template message?
雪糕和轮胎
Flutter "Hello world" program code
Unity在BuildIn渲染管线下实现PlanarReflection的初级方法
How to write a high-quality digital good article recommendation
safari浏览器怎么导入书签
The 16th day of the special assault version of the sword offer
<JDBC> 批量插入 的四种实现方式:你真的get到了吗?
动态规划 01背包
【愚公系列】2022年07月 Go教学课程 023-Go容器之列表
【搜索专题】看完必会的BFS解决最短路问题攻略
云服务器下载安装mongo数据库并远程连接详细图文版本(全)
【kali-信息收集】枚举——DNS枚举:DNSenum、fierce
/etc/fstab