API设计笔记:pimpl技巧

网友投稿 261 2022-09-04

API设计笔记:pimpl技巧

pimpl

pointer to implementation:指向实现的指针,使用该技巧可以避免在头文件暴露私有细节,可以促进API接口和实现保持完全分离。

下面是一个自动定时器的API,会在被销毁时打印其生存时间。

原有api

// autotimer.h#ifdef _WIN32#include #else#include #endif#include 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、暴露了定时器在不同平台上存储的底层细节

设计者真正的目的是将所有的私有成员隐藏在.cpp文件中,这样我们可以使用Pimpl惯用法了。

autotimer.h

// autotimer.h#include 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 #if _WIN32#include #else#include #endifclass 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对象并初始化其成员,而析构函数负责销毁该对象。

// autotimer.h#include class AutoTimer {public: explicit AutoTimer(const std::string& name); ~AutoTimer(); class Impl;private: Impl* mImpl;};

如何规划Impl类中的逻辑?

注意事项:

不能在Impl类中隐藏私有虚函数,虚函数必须出现在公有类中,从而保证任何派生类都能重写他们

pimpl的复制语义

在c++中,如果没有给类显式定义复制构造函数和赋值操作符,C++编译器默认会创建,但是这种默认的函数只能执行对象的浅复制,这不利于类中有指针成员的类。

如果客户复制了对象,则两个对象指针将指向同一个Impl对象,两个对象可能在析构函数中尝试删除同一个对象两次从而导致崩溃。

下面提供了两个解决思路:

2、显式定义复制语义

#include class AutoTimer{public: explicit AutoTimer(const std::string& name); ~AutoTimer();private: AutoTimer(const AutoTimer&); const AutoTimer &operator=(const AutoTimer&); class Impl; Impl* mImpl;}

智能指针优化Pimpl

#include class AutoTimer{public: explicit AutoTimer(const std::string& name); ~AutoTimer();private: class Impl; boost::scoped_ptr mImpl; // 如果使用shared_ptr就需要自己编写复制构造和操作符}

Pimpl优缺点总结

优点:

1、信息隐藏

2、降低耦合

3、加速编译:实现文件移入.cpp降低了api的引用层次,直接影响编译时间

4、二进制兼容性:任何对于成员变量的修改对于Pimpl对象指针的大小总是不变

5、惰性分配:mImpl类可以在需要时再构造

缺点:

1、增加了Impl类的分配和释放,可能会引入性能冲突。

2、访问所有私有成员都需要在外部套一层mImpl→,这会使得代码变得复杂。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:最长算术(暑假每日一题 11)
下一篇:双节营销应该这样做!(双节营销文案)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~