首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PIMPL:休想窥探我的隐私!

PIMPL:休想窥探我的隐私!

作者头像
编程珠玑
发布2020-09-27 10:44:49
3430
发布2020-09-27 10:44:49
举报
文章被收录于专栏:编程珠玑编程珠玑

前言

有时候我们需要提供对外的API,通常会以头文件的形式提供。举个简单的例子: 提供一个从某个指定数开始打印的接口,头文件内容如下:

//来源:公众号编程珠玑
//作者:守望先生
#ifndef _TEST_API_H
#define _TEST_API_H
//test_api.h
class TestApi{
  public:
    TestApi(int s):start(s){}
    void TestPrint(int num);
  private:
    int start_ = 0;
};
#endif //_TEST_API_H

实现文件如下:

//来源:公众号编程珠玑
//作者:守望先生
#include "test_api.h"
#include <iostream>
//test_api.cc
TestApi::TestPrint(int num){
  for(int i = start_; i < num; i++){
    std::cout<< i <<std::endl;
  }
}

类TestApi中有一个私有变量start_,头文件中是可以看到的。

#include "test_api.h"
int main(){
  TestApi test_api{10};
  test_api.TestPrint(15);
  return 0;
}

常规实现缺点

从前面的内容来看, 一切都还正常,但是有什么问题呢?

  • 头文件暴露了私有成员
  • 实现与接口耦合
  • 编译耦合

第一点可以很明显的看出来,其中的私有变量star_能否在头文件中看到,如果实现越来越复杂,这里可能也会出现更多的私有变量。有人可能会问,私有变量外部也不能访问,暴露又何妨?

不过你只是提供几个接口,给别人看到这么多信息干啥呢?这样就会导致实现和接口耦合在了一起。

另外一方面,如果有另外一个库使用了这个库,而你的这个库实现变了,头文件就会变,而头文件一旦变动,就需要所有使用了这个库的程序都要重新编译!

这个代价是巨大的。

所以,我们应该尽可能地保证头文件不变动,或者说,尽可能隐藏实现,隐藏私有变量。

PIMPL

Pointer to implementation,由指针指向实现,而不过多暴露细节。废话不多说,上代码:

//来源:公众号编程珠玑
//作者:守望先生
#ifndef _TEST_API_H
#define _TEST_API_H
#include <memory>
//test_api.h
class TestApi{
  public:
    TestApi(int s);
    ~TestApi();
    void TestPrint(int num);
  private:
    class TestImpl;
    std::unique_ptr<TestImpl> test_impl_;
};
#endif //_TEST_API_H

从这个头文件中,我们可以看到:

  • 实现都在TestImpl中,因为只有一个私有的TestImpl变量,可以预见到,实现变化时,这个头文件是基本不需要动的
  • test_impl_是一个unique_ptr,因为我们使用的是现代C++,这里需要注意的一点是,它的构造函数和析构函数必须存在,否则会有编译问题。

我们再来看下具体的实现:

//来源:公众号编程珠玑
//作者:守望先生
#include "test_api.h"
#include <iostream>
//test_api.cc
class TestApi::TestImpl{
  public:
    void TestPrint(int num);
    TestImpl(int s):start_(s){}
    TestImpl() = default;
    ~TestImpl() = default;
  private:
    int start_;
};

void TestApi::TestImpl::TestPrint(int num){
  for(int i = start_; i < num; i++){
    std::cout<< i <<std::endl;
  }
}

TestApi::TestApi(int s){
    test_impl_.reset(new TestImpl(s));
}

void TestApi::TestPrint(int num){
  test_impl_->TestPrint(num);
}
//注意,析构函数需要
TestApi::~TestApi() = default;

从实现中看到,TestApi中的TestPrint调用了TestImpl中的TestPrint实现,而所有的具体实现细节和私有变量都在TestImpl中,即便实现变更了,其他库不需要重新编译,而仅仅是在生成可执行文件时重新链接。

总结

从例子中,我们可以看到PIMPL模式中有以下优点:

  • 降低耦合,头文件稳定,类具体实现改变不影响其他模块的编译,只影响可执行程序的链接
  • 接口和实现分离,提高稳定性

当然了,由于实现在另外一个类中,所以会多一次调用,会有性能的损耗,但是这点几乎可以忽略。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-09-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程珠玑 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 常规实现缺点
  • PIMPL
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档