当前位置:网站首页>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→,这会使得代码变得复杂。
边栏推荐
- leetcode6132. Make all elements in an array equal to zero (simple, weekly)
- [SemiDrive source code analysis] series article link summary (full)
- 2022-07-31: Given a graph with n points and m directed edges, you can use magic to turn directed edges into undirected edges, such as directed edges from A to B, with a weight of 7.After casting the m
- Weekly Summary (*67): Why not dare to express an opinion
- Character encoding and floating point calculation precision loss problem
- August 22 Promotion Ambassador Extra Reward Rules
- Mysql基础篇(Mysql数据类型)
- 请问shake数据库中想把源的db0的数据同步到目的db5,参数怎么设置呢?
- SQL Analysis of ShardingSphere
- 动态规划 01背包
猜你喜欢
【愚公系列】2022年07月 Go教学课程 025-递归函数
基于ProXmoX VE的虚拟化家庭服务器(篇一)—ProXmoX VE 安装及基础配置
【搜索专题】看完必会的BFS解决最短路问题攻略
button remove black frame
Article summary: the basic model of VPN and business types
Invalid classes inferred from unique values of `y`. Expected: [0 1 2], got [1 2 3]
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.
FFmpeg 搭建本地屏幕录制环境
button去除黑框
对无限debugger的一种处理方式
随机推荐
Excel做题记录——整数规划优化模型
【愚公系列】2022年07月 Go教学课程 024-函数
Simple and easy to use task queue - beanstalkd
软件测试周刊(第82期):其实所有纠结做选择的人心里早就有了答案,咨询只是想得到内心所倾向的选择。
Make your Lottie support word wrapping in text fields
IJCAI2022 | Hybrid Probabilistic Reasoning with Algebraic and Logical Constraints
EntityFramework saves to SQLServer decimal precision is lost
每周小结(*67):为什么不敢发表观点
怀念故乡的面条
请问表格储存中用sql只能查询到主键列,ots sql非主键不支持吗?
软件测试面试(三)
Take you to experience a type programming practice
TypeScript简化运行之ts-node
<JDBC> 批量插入 的四种实现方式:你真的get到了吗?
数据比对功能调研总结
雪糕和轮胎
项目风险管理必备内容总结
动态规划 01背包
让你的 Lottie 支持文字区域内自动换行
【SemiDrive源码分析】系列文章链接汇总(全)