Pimpl惯用法
Pimpl术语,即“pointer to implementation”(指向实现的指针),由Jeff Summer最先引入。该技巧可以避免在头文件中暴露私有细节,是促进API接口和实现保持完全分离的重要机制。(在将接口提供给别人时经常使用的一个技巧)
Pimpl并不是严格意义上的设计模式,而是桥接模式的一种特例。
Pimpl将类的数据成员定义为指向某个已经声明过的类型的指针,如上面例子中的impl。这里,类型仅作为名字引入,没有被完整定义,只需要前向声明即可,隐藏可以将该类型定义隐藏在.cpp文件中。该指针也称为不透明指针,因为用户无法看到所指对象细节。
示例
:“自动定时器”(AutoTimer)API,对象被销毁时打印出生存时间。
// autotimer.h
#include <string>
class AutoTimer // 接口类
{
public:
explicit AutoTimer(const std::string &name);
~AutoTimer();
private:
class Impl; // 前置声明
Impl *impl; // 通过私有内嵌类, 将实现细节封装到内嵌类中
};
// 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() - startTime_) / 1e3;
#else
struct timeval end_time;
gettimeofday(&end_time, NULL);
double t1 = startTime_.tv_usec / 1e6 + startTime_.tv_sec;
double t2 = end_time.tv_usec / 1e6 + end_time.tv_sec;
return t2 - t1;
#endif
}
std::string name_;
#ifdef _WIN32
DWORD starTime_;
#else
struct timeval startTime_;
#endif
};
AutoTimer::AutoTimer(const std::string &name) :
impl_(new AutoTimer::Impl())
{
impl_->name_ = name;
#ifdef _WIN32
imp_->startTime_ = GetTickCount();
#else
gettimeofday(&impl_->startTime, NULL);
#endif
}
AutoTimer::~AutoTimer()
{
std::cout << impl_->name_ << ": took " << impl_->GetElapsed()
<< " secs" << std::endl;
delete impl_;
impl_ = NULL;
}
Pimpl与智能指针
使用裸指针指向Impl类,容易忘记在析构时delete对象,或者在对象分配前就进行访问,从而造成错误。可以借助智能指针(smart pointer)解决该问题,具体来说,可采用shared_ptr(共享指针),或unique_ptr(域指针)指向Impl类对象。
两者区别:共享指针允许用户复制(接口类)对象,域指针不允许用户复制。
#include <memory>
#include <string>
class AutoTimer
{
public:
explicit AutoTimer(const std::string &name);
~AutoTimer();
private:
class Impl;//类的前置声明
std::unique_ptr<Impl> impl_;
};
桥接模式
Bridge是一种结构设计模式,可让您将一个大类或一组密切相关的类拆分为两个独立的层次结构(抽象和实现),这两个层次结构可以彼此独立开发。
定义比较抽象,可以认为,提供接口API 动态库,确定接口后不再修改,就是桥接模式的简单实现
调用方可以自由扩张,实现方也可以自由扩展,并且使用动态,在部署的时候,可以分别部署。
项目初期阶段确定接口
调用方在编译的时候有一个旧版本,或者实现一个假接口就可以开发。接口提供方可以同步进行开发(这个在项目中是个很普遍的做法)