API设计笔记:pimpl技巧

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 _WIN32DWORD mStartTime;
#elsestruct 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>
#endifclass AutoTimer::Impl 
{
public:double GetElapsed() const{
#ifdef _WIN32return (GetTickCount() - mStartTime) / 1e3;
#elsestruct 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 _WIN32DWORD mStartTime;
#elsestruct timeval mStartTime;
#endif
};AutoTimer::AutoTimer(const std::string& name) : mImpl(new AutoTimer::Impl())
{mImpl->mName = name;
#ifdef _WIN32mImpl->mStartTime = GetTickCount();
#elsegettimeofday(&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→,这会使得代码变得复杂。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/376525.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《设计模式》-责任链模式

责任链模式是一种对象的行为模式【GOF95】。在责任链模式里&#xff0c;很多对象由每一个对象对其下家的用而链起来形成一条链&#xff0c;请求在这个链上传递&#xff0c;直到链上的某一个对象决定处理此请求。 发出请求的客户端并不知道链上的哪一个对象终处理这个请求&#…

【机器学习】EM最大期望算法

EM, ExpectationMaximization Algorithm, 期望最大化算法。一种迭代算法&#xff0c;用于含有隐变量(hidden variable)的概率参数模型的最大似然估计或极大后验概率估计&#xff0c;其概率模型依赖于无法观测的隐变量。 经常用在ML与计算机视觉的数据聚类领域。 EM应用&#xf…

做一个给自己手机免费发送“天气预报”信息的软件

实现一个以下截图这样的功能&#xff01;没错&#xff0c;就是你手机可以收到“免费”的天气预报短信&#xff01; 一、在做之前必须了解以下四个功能&#xff1a; 1、WebService 2、Quartz.Net&#xff08;定时任务框架&#xff09; 3、SMTP&#xff1a;简单邮件传输协议,它是…

Android_Chronometer计时器

最近做一个项目用到Handler 和Message &#xff0c;开始时不是很明白&#xff0c;不了解其中的内部机制&#xff0c;所以开发起来有点难度&#xff0c;之后自己找了Android 时间服务 这一节的内容&#xff0c;总结了一点关于时间的知识&#xff0c;在这里大概写一下&#xff0c…

置顶 | wolai博客

最近用wolai记录笔记较多&#xff0c;这里放一下我wolai的地址&#xff0c;当然csdn这边也会同时更文。 hanhan的博客

为你的程序添加监听器

平时在写程序时经常会遇到监听器&#xff0c;比如按钮的click监听器&#xff0c;按键监听器等等。而android中的监听器和java中的回调函数是同一个概念&#xff0c;都是在底层代码中定义一个接口来调用高层的代码。那么什么是回调函数呢&#xff1f;网上说的是“在WINDOWS中&am…

Git push 时每次都需要密码的疑惑

2015.1.13更新&#xff1a; 在本地搭建Git服务器时&#xff0c;也是有每次操作需要密码的情况。 是因为每次做推送动作时&#xff0c;Git需要认证你是好人。所以需要密码。 可以在 /home/username/.ssh/authorized_keys 文件里添加你的 ssh 公钥。一行一个。这样就可以在你push…

【PS】Gold words tutorials 赤金字教程

material_01material_021. White background and black words.The font of "Laker" is Teenick, and "Huang" is 中國龍粗魏碑2.Open material_01 and select a part of it.Copy and paste the part part into our workspace.You can drag and move to pa…

Android中的Handler机制

直接在UI线程中开启子线程来更新TextView显示的内容&#xff0c;运行程序我们会发现&#xff0c;如下错 误&#xff1a;android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.翻译过来就是&…

并行编程——内存模型之顺序一致性

1 定义 Sequential consistency , 简称 SC&#xff0c;定义如下 … the result of any execution is the same as if the operations of all the processors were executed in some sequential order, and the operations of each individual processor appear in this sequen…

Daily Scrum 11.18

今日完成任务&#xff1a; 1.在提问问题的时候为问题创建索引 2.解决了修改个人资料后刷新没有更新的问题 3.初步加入了采纳功能&#xff08;没完善UI设计&#xff09; 遇到困难&#xff1a;创建索引之后&#xff0c;跳转到主页&#xff0c;需要重新登录&#xff0c;找了半天不…

hyper-v 用户无法再 创建外部配置存储 0x80070005

windows server 2008R2 刚安装的hyper-v 重启过。 修改配置文件到d:\Hyper-V目录下&#xff0c; hyper-V 创建 服务器遇到错误 操作失败 创建外部配置存储:一般性拒绝访问错误 虚拟机ID 0x80070005 d:\hyper-V 安全权限为 everyone 所有&#xff0c;users 所有&#xff0c;admi…

C++ 虚函数表解析

C 虚函数表解析 陈皓 http://blog.csdn.net/haoel 前言 C中的虚函数的作用主要是实现了多态的机制。关于多态&#xff0c;简而言之就是用父类型别的指针指向其子类的实例&#xff0c;然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”&#x…

HTTP协议 (四) 缓存

HTTP协议 (四) 缓存 阅读目录 缓存的概念缓存的好处Fiddler可以方便地查看缓存的header如何判断缓存新鲜度通过最后修改时间&#xff0c;判断缓存新鲜度与缓存相关的headerETag浏览器不使用缓存直接使用缓存&#xff0c;不去服务器端验证如何设置IE不使用缓存公有缓存和私有缓存…

ZooKeeper启动过程2:FastLeaderElection

前一篇文章中说到&#xff0c;启动ZooKeeper集群时&#xff0c;需要分别启动集群中的各个节点&#xff0c;各节点以QuorumPeer的形式启动&#xff0c;最后到达startLeaderElection和lookForLeader。 先说startLeaderElection 首先&#xff0c;初始化节点自身的currentVote【当前…

遮罩效果 css3

CSS3提供了遮罩效果&#xff0c;这是以前CSS2中比较难实现的一个新特性&#xff0c;配合SVG或者canvas同样也可以实现遮罩效果&#xff0c;他的效果就如下图所示: 简单的说就是在一个层上面加一个过滤层&#xff0c;过滤层透明度越低&#xff0c;底层就显示的越多&#xff0c;反…

配置SQLServer,允许远程连接

需要别人远程你的数据库&#xff0c;首先需要的是在一个局域网内&#xff0c;或者连接的是同一个路由器&#xff0c;接下来就是具体步骤&#xff1a; &#xff08;一&#xff09;首先是要检查SQLServer数据库服务器中是否允许远程链接。其具体操作为&#xff1a; &#xff08;1…

聚类算法初探(八)数据尺度化问题

文中尺度化的一些具体公式可参见 http://blog.csdn.net/itplus/article/details/10088101 其他相关链接 引言 预备知识 直接聚类法 K-means DBSCAN OPTICS 聚类分析的效果评测 作者: peghoty 出处: http://blog.csdn.net/itplus/article/details/10484553 欢迎转载/分享, 但请…

【待完善】make: command not found,以及libtool.m4 and ltmain.sh have a version mismatch问题的解决方案...

之前为了使用一个库&#xff0c;都是去下载源码&#xff0c;然后根据开发者提供的README手动用GCC编译&#xff0c;一直不能使用Makefile感觉很蛋痛&#xff0c;比如最近使用的ZThread 还是怪自己以前过于依赖IDE 最近发现用Cygwin就可以使用诸如./configure, make这样的命令&a…

菜鸟nginx源码剖析

菜鸟nginx源码剖析 配置与部署篇&#xff08;一&#xff09; 手把手配置nginx “I love you” TCMalloc 对MYSQL 性能 优化的分析 菜鸟nginx源码剖析系列文章解读 Author&#xff1a;Echo Chen&#xff08;陈斌&#xff09; Email&#xff1a;chenb19870707gmail.com Blog&…