C++ pimpl机制详细讲解

目录
  • 什么是PImpl机制
  • 为什么用PImpl 机制
  • PImpl实现
    • 方法一
    • 方法二
  • PImpl 缺点
  • 总结

源码仓库

什么是PImpl机制

Pointer to implementation(PImpl ),通过将类的实现细节放在一个单独的类中,从其对象表示中删除它们,通过一个不透明的指针访问它们(cppreference 是这么说的)

通过一个私有的成员指针,将指针所指向的类的内部实现数据进行隐藏

class Demo {
public:
	...
private:
	DemoImp* imp_;
}

为什么用PImpl 机制

个人拙见

  • C++ 不像Java 后端型代码,能有行业定式的列目录名形成规范(controller、Dao等)
  • 隐藏实现,降低耦合性和分离接口(隐藏类的具体实现)
  • 通过编译期的封装(隐藏实现类的细节)

业界实现

优秀开源代码有实现

PImpl实现

方法一

cook_cuisine.h

#pragma once
#include <unordered_map>
#include <vector>
#include <memory>
//  Pointer to impl ementation
class CookImpl;
// 后厨
class Cook {
public:
    Cook(int, const std::vector<std::string>&);
    ~Cook();
    std::vector<std::string> getMenu();     /* 获取菜单 */
    uint32_t getChefNum();                  /* 获取厨师数量 */
private:
    CookImpl* impl_;
};
typedef std::shared_ptr<Cook> CookPtr;		// 美妙的typedef 懒人工具

cook_cuisine.cc

#include "cook_cuisine.h"
class CookImpl {
public:
    CookImpl(uint32_t checf_num, const std::vector<std::string>& menu):checf_num_(checf_num), menu_(menu) {}
    std::vector<std::string> getMenu();
    uint32_t getChefNum();
private:
    uint32_t checf_num_;
    std::vector<std::string> menu_;
};
std::vector<std::string> CookImpl::getMenu() {
    return menu_;
}
uint32_t CookImpl::getChefNum() {
    return checf_num_;
}
Cook::Cook(int chef_num, const std::vector<std::string>& menu) {
    impl_ = new CookImpl(chef_num, menu);
}
Cook::~Cook() {
    delete impl_;
}
std::vector<std::string> Cook::getMenu() {
    return impl_->getMenu();
}
uint32_t Cook::getChefNum() {
    return impl_->getChefNum();
}

方法二

cook_cuisine.h

#pragma once
#include <unordered_map>
#include <vector>
#include <memory>
#include "cook_cuisine_imp.h"
// 后厨
class Cook {
public:
    Cook(int, const std::vector<std::string>&);
    ~Cook();
    std::vector<std::string> getMenu();     /* 获取菜单 */
    uint32_t getChefNum();                  /* 获取厨师数量 */
private:
    CookImplPtr impl_;
};
typedef std::shared_ptr<Cook> CookPtr;

cook_cuisine.cc

#include "cook_cuisine.h"
Cook::Cook(int chef_num, const std::vector<std::string>& menu) {
    impl_.reset(new CookImpl(chef_num, menu));
}
Cook::~Cook() {
}
std::vector<std::string> Cook::getMenu() {
    return impl_->getMenu();
}
uint32_t Cook::getChefNum() {
    return impl_->getChefNum();
}

cook_cuisine_imp.h

#pragma once
#include <vector>
#include <unordered_map>
#include <memory>
class CookImpl {
public:
    CookImpl(uint32_t checf_num, const std::vector<std::string>& menu):checf_num_(checf_num), menu_(menu) {}
    std::vector<std::string> getMenu();
    uint32_t getChefNum();
private:
    uint32_t checf_num_;
    std::vector<std::string> menu_;
};
typedef std::shared_ptr<CookImpl> CookImplPtr;

cook_cusine_imp.cc

#include "cook_cuisine_imp.h"
std::vector<std::string> CookImpl::getMenu() {
    return menu_;
}
uint32_t CookImpl::getChefNum() {
    return checf_num_;
}

main.cc

#include "cook_cuisine.h"
#include <iostream>
using namespace std;    // Testing, 平时开发可千万别用这句
int main() {
    int checf_num = 10;
    const std::vector<std::string> menus = { "Chicken", "Beef", "Noodle", "Milk" };
    CookPtr cook(new Cook(checf_num, menus));
    auto cook_menu = cook->getMenu();
    auto cook_checf_num = cook->getChefNum();
    cout << "======================Chinese Cook======================\n";
    cout << "============Checf: " << cook_checf_num << " people\n";
    cout << "==========Menu\n";
    for (size_t i = 0; i < cook_menu.size(); i++) {
        cout << "============" << i + 1 << " : " << cook_menu[i] << "\n";
    }
    return 0;
}

CMakeLists.txt

mkdir build
cd build
cmake ..

PImpl 缺点

空间开销:每个类都需要额外的指针内存指向实现类

时间开销:每个类间接访问实现的时候多一个间接指针操作的开销

阅读开销:使用、阅读和调试上带来一些不便(不是啥问题)

总结

每种设计方法都有它的优点和缺点

PImpl 用一些内存空间和额外类的实现换取耦合性的下降,是可以接受的

但重点在:在性能/内存要求不敏感处,PImpl 技术才更优不错的发挥舞台

极端例子:

你不可能在斐波那契的实现中还加个PImpl 机制,多此一举

到此这篇关于C++ pimpl机制详细讲解的文章就介绍到这了,更多相关C++ pimpl机制内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++详解PIMPL指向实现的指针

    目录 二进制兼容性 功能实现细节隐藏 编译依赖 动态配置功能的实现方法 二进制兼容性 ①.概述 二进制兼容是指当库文件升级后所有使用该库的应用程序不必重新编译,其本质就是类的内存布局不变.使用 pimpl 方法设计类可以实现二进制兼容的目的. ②.类成员更改后的内存布局 原始类定义: class demoClass { private: int a; int b; }; 类更改后的定义: class demoClass { private: char c; int a; int b; }; ②.

  • C++学习笔记之pimpl用法详解

    前言 本文主要给大家介绍了关于C++中pimpl用法的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: C++的pImpl可以说是最常见的惯用手法了,在很多的C++项目和C++开发库中都有所见.plmp的缩写就是Pointer to Implementor,顾名思义就是将真正的实现细节的Implementor从类定义的头文件中分离出去,公有类通过一个私有指针指向隐藏的实现类,是促进接口和实现分离的重要机制. 在C++语言中,要定义某个类型的变量或者使用类型的某个成员,就必

  • C++ pimpl机制详细讲解

    目录 什么是PImpl机制 为什么用PImpl 机制 PImpl实现 方法一 方法二 PImpl 缺点 总结 源码仓库 什么是PImpl机制 Pointer to implementation(PImpl ),通过将类的实现细节放在一个单独的类中,从其对象表示中删除它们,通过一个不透明的指针访问它们(cppreference 是这么说的) 通过一个私有的成员指针,将指针所指向的类的内部实现数据进行隐藏 class Demo { public: ... private: DemoImp* imp_

  • java ClassLoader机制详细讲解

    要深入了解ClassLoader,首先就要知道ClassLoader是用来干什么的,顾名思义,它就是用来加载Class文件到JVM,以供程序使用的.我们知道,java程序可以动态加载类定义,而这个动态加载的机制就是通过ClassLoader来实现的,所以可想而知ClassLoader的重要性如何. 看到这里,可能有的朋友会想到一个问题,那就是既然ClassLoader是用来加载类到JVM中的,那么ClassLoader又是如何被加载呢?难道它不是java的类? 没有错,在这里确实有一个Class

  • 详细讲解HDFS的高可用机制

    目录 互斥机制 写流程 读流程 恢复流程 在Hadoop2.X之前,Namenode是HDFS集群中可能发生单点故障的节点,每个HDFS集群只有一个namenode,一旦这个节点不可用,则整个HDFS集群将处于不可用状态. HDFS高可用(HA)方案就是为了解决上述问题而产生的,在HA HDFS集群中会同时运行两个Namenode,一个作为活动的Namenode(Active),一个作为备份的Namenode(Standby).备份的Namenode的命名空间与活动的Namenode是实时同步的

  • Java 超详细讲解Spring MVC异常处理机制

    目录 异常处理机制流程图 异常处理的两种方式 简单异常处理器SimpleMappingExceptionResolver 自定义异常处理步骤 本章小结 异常处理机制流程图 系统中异常包括两类: 预期异常 通过捕获异常从而获取异常信息. 运行时异常RuntimeException 主要通过规范代码开发.测试等手段减少运行时异常的发生. 系统的Dao.Service.Controller出现都通过throws Exception向上抛出,最后SpringMVC前端控制器交由异常处理器进行异常处理,如

  • SpringBoot详细讲解断言机制原理

    目录 1.简单断言 2.数组断言 3.组合断言 4.异常断言 5.超时断言 6.快速失败 JUnit 5 内置的断言可以分成如下几个类别: 1.简单断言 用来对单个值进行简单的验证.如: 方法 说明 assertEquals 判断两个对象或两个原始类型是否相等 assertNotEquals 判断两个对象或两个原始类型是否不相等 assertSame 判断两个对象引用是否指向同一个对象 assertNotSame 判断两个对象引用是否指向不同的对象 assertTrue 判断给定的布尔值是否为

  • Python超详细讲解内存管理机制

    目录 什么是内存管理机制 一.引用计数机制 二.数据池和缓存 什么是内存管理机制 python中创建的对象的时候,首先会去申请内存地址,然后对对象进行初始化,所有对象都会维护在一 个叫做refchain的双向循环链表中,每个数据都保存如下信息: 1. 链表中数据前后数据的指针 2. 数据的类型 3. 数据值 4. 数据的引用计数 5. 数据的长度(list,dict..) 一.引用计数机制 引用计数增加: 1.1 对象被创建 1.2 对象被别的变量引用(另外起了个名字) 1.3 对象被作为元素,

  • Spring超详细讲解事务和事务传播机制

    目录 为什么需要事务 Spring 声明事务 Transactional参数说明 propagation isolation timeout 事务回滚失效解决方案 @Transactional工作原理 Spring 事务的传播机制 为什么需要事务传播机制? 传播机制的类型 为什么需要事务 事务是将一组操作封装成一个执行单元,要么全部成功,要么全部失败.如果没有事务,转账操作就会出现异常,因此需要保证原子性. Spring 声明事务 只需要在方法上添加@Transactional注解就可以实现,无

  • Spring Boot超详细讲解请求处理流程机制

    目录 1. 背景 2. Spring Boot 的请求处理流程设计 3. Servlet服务模式请求流程分析 3.1 ServletWebServerApplicationContext分析 3.2 Servlet服务模式之请求流程具体分析 4. Reactive服务模式请求流程分析 4.1 ReactiveWebServerApplicationContext分析 4.2 webflux服务模式之请求流程具体分析 5. 总结 1. 背景 之前我们对Spring Boot做了研究讲解,我们知道怎

  • MyBatis插件机制超详细讲解

    目录 MyBatis的插件机制 InterceptorChain MyBatis中的Plugin MyBatis插件开发 总结 MyBatis的插件机制 MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用.默认情况下,MyBatis 允许使用插件来拦截的方法调用包括: Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) ParameterHandler(

  • Android同步异步任务与多线程及Handler消息处理机制基础详细讲解

    目录 一.同步与异步 Android中的多线程 Android中的多线程与主线程与子线程 Handler异步通信系统 使用新线程计算质数 一.同步与异步 同步的执行任务:在执行程序时,如果没有收到执行结果,就一直等,不继续往下执行,直到收到执行结果,才接着往下执行. 异步的执行任务:在执行程序时,如果遇到需要等待的任务,就另外开辟一个子线程去执行它,自己继续往下执行其他程序.子线程有结果时,会将结果发送给主线程 Android中的多线程 线程:通俗点讲就是一个执行过程.多线程自然就是多个执行过程

随机推荐