impl模式早就有过接触(本文特指通过指针完成impl),我晓得它具有以下优点:

减少头文件暴露出来的非必要内部类(提供静态库,动态库时尤其重要);

减小文件间的编译依存关系,大型代码库的编译时间就不会那么折磨人了。

impl会带来性能的损耗,每次访问都因为指针增加了间接性,还有一个微小的指针内存消耗。但是基于以上优点,除非你十分确定它造成了性能损耗,否则就让它存在吧。

qt中大量使用impl,具体可见https://wiki.qt.io/d-pointer中关于q_d和q_q宏的解释。

然而,如何使用智能指针,我是说基于std::unique_ptr实现正确的impl模式,就有点意思了。

错误做法

#include

#include

class trace1 : public boost::noncopyable {

public:

trace1();

~trace1() = default;

void test();

private:

class traceimpl;

std::unique_ptr _impl;

};

这是我初版代码,关于_impl的实现细节,存放于cpp中,如下所示:

class trace1::traceimpl {

public:

traceimpl() = default;

static std::string test() {

return "hello trace1";

}

};

trace1::trace1() :

_impl(std::make_unique<:traceimpl>()) {

}

void trace1::test() {

std::cout << _impl->test() << std::endl;

}

很无情,我遇到了错误,错误如下所示:

为什么会这样呢,报错信息提示traceimpl是一个不完整的类型。

其实,就是编译器看到traceimpl,无法在编译期间确定traceimpl的大小。此处我们使用的是std::unique_ptr,其中存放的是一个指针,没必要知道traceimpl的具体大小(换成std::shared_ptr就不会这个报错)。

错误分析

往上看报错信息,发现std::unique_ptr的析构函数有点意思:

/usr/include/c++/7/bits/unique_ptr.h: in instantiation of ‘void std::default_delete<_tp>::operator()(_tp*) const [with _tp = trace1::traceimpl]’:

/usr/include/c++/7/bits/unique_ptr.h:268:17: required from ‘std::unique_ptr<_tp _dp>::~unique_ptr() [with _tp = trace1::traceimpl; _dp = std::default_delete<:traceimpl>]’

/home/jinxd/clionprojects/impltest/include/trace1.h:16:5: required from ‘void std::default_delete<_tp>::operator()(_tp*) const [with _tp = trace1]’

/usr/include/c++/7/bits/unique_ptr.h:268:17: required from ‘std::unique_ptr<_tp _dp>::~unique_ptr() [with _tp = trace1; _dp = std::default_delete]’

报错信息中,有两段提到了析构函数,而且都是默认析构函数:std::default_delete<_tp>。应该知道,我们的代码在编译的时候,会被编译器往里面添加点作料。按照c++的哲学就是,你不需要知道我们添加了什么,你只需要晓得添加后的结果是什么。可是,为了解决错误,我们必须知道大概添加了什么。

代码中,trace1的析构函数标记为default,函数体中无具体代码,trace1的析构函数有很大的可能性被inline了。如果函数被inline了,那么引用trace1.h的main文件中,析构函数会被文本段落展开。

以前我就就在想,析构函数中没有代码,展开也不应该产生影响。错就错在,编译之后的析构函数被扩展了,塞入了_impl的销毁代码。销毁_impl必然会调用到std::unique_ptr的析构函数。std:unique_ptr在销毁的时候,会调用构造函数中传来的析构函数(如果你没有显式提供析构函数,那么就是用编译器扩展的默认析构函数)。此处调用traceimpl的默认析构函数,发现类只有前置声明(具体实现在trace1.cpp文件中,main中没有引入此文件),因此不知道traceimpl的实际大小。

问题出来了,为什么需要知道traceimpl的实际大小呢?可以认为c++中的new是malloc的封装,执行new的时候,其实就是根据类的大小malloc固定大小的空间,反之,delete也就是释放掉指定大小的空间。你不提供声明,这就让编译器很为难,只能报错了。

解决方式

解决方式很简单,一切都是inline引起的,那么我们就让析构函数outline。通过这种方式,将trace1的析构函数实现转移至trace1.cpp中,从而发现traceimpl的具体实现。代码如下所示:

// trace1.h

class trace1 : public boost::noncopyable {

public:

trace1();

~trace1();

void test();

private:

class traceimpl;

std::unique_ptr _impl;

};

// trace1.cpp

class trace1::traceimpl {

public:

traceimpl() = default;

static std::string test() {

return "hello trace1";

}

};

trace1::trace1() :

_impl(std::make_unique<:traceimpl>()) {

}

trace1::~trace1() = default;

void trace1::test() {

std::cout << _impl->test() << std::endl;

}

如此操作,析构函数就可以看见traceimpl的声明,于是就能正确的执行析构操作。

换个姿势

上文中提及了,std::unique_ptr的构造函数中,第二个入参其实是一个仿函数,那么我们也可以通过仿函数解决这个问题,代码如下所示:

// trace2.h

class trace2 : public boost::noncopyable {

public:

trace2();

~trace2() = default;

void test();

private:

class traceimpl;

class traceimpldeleter {

public:

void operator()(traceimpl *p);

};

std::unique_ptr _impl;

};

// trace2.cpp

class trace2::traceimpl {

public:

traceimpl() = default;

static std::string test() {

return "hello trace2";

}

};

void trace2::traceimpldeleter::operator()(trace2::traceimpl *p) {

delete p;

}

trace2::trace2() :

_impl(new trace2::traceimpl, trace2::traceimpldeleter()) {

}

void trace2::test() {

std::cout << _impl->test() << std::endl;

}

是的,仿函数的实现置于trace2.cpp中,完美解决问题。

不过我不喜欢这样的写法,因为没法使用std::make_unique初始化_impl,原因就这么简单。

ps:

如果您觉得我的文章对您有帮助,请关注我的微信公众号,谢谢!

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

impl java_究竟是什么毁了我的impl实现相关推荐

  1. lmpl java_究竟是什么毁了我的impl实现

    Impl模式早就有过接触(本文特指通过指针完成impl),我晓得它具有以下优点: 减少头文件暴露出来的非必要内部类(提供静态库,动态库时尤其重要): 减小文件间的编译依存关系,大型代码库的编译时间就不 ...

  2. 安装pycharm报nse.impl.GeneralLicenseManager - No valid license found错解决方案

    安装全pycharm后执行报错,信息如下: 点击(此处)折叠或打开 [t@bjb0541 ~]$ pycharm-professional OpenJDK 64-Bit Server VM warni ...

  3. 定时任务管理系统 java_几种任务调度的 Java 实现方法与比较(定时任务)(转)...

    转自:http://blog.csdn.net/javafay/article/details/8031269 综观目前的 Web 应用,多数应用都具备任务调度的功能.本文由浅入深介绍了几种任务调度的 ...

  4. mahout 推荐算法 java_推荐系统之推荐算法实战:mahout推荐算法框架

    1.Mahout介绍 1.1概述 根据百度的解说,Mahout 是 Apache Software Foundation(ASF) 旗下的一个开源项目,提供一些可扩展的机器学习领域经典算法的实现,旨在 ...

  5. org.eclipse.aether.internal.impl.DefaultArtifactResolver.setLoggerFactory

    idea2018.1.5版本,使用maven3.6 构建工程时,右侧Dependencies全是红叉,删除所有文件,重新导入也一样.此问题折腾我两天,最后通过查看idea执行日志, 报如下错: org ...

  6. Spring-AOP实践 - 统计访问时间

    公司的项目有的页面超级慢,20s以上,不知道用户会不会疯掉,于是老大说这个页面要性能优化.于是,首先就要搞清楚究竟是哪一步耗时太多. 我采用spring aop来统计各个阶段的用时,其中计时器工具为S ...

  7. 《JAVA与模式》之桥梁模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述桥梁(Bridge)模式的: 桥梁模式是对象的结构模式.又称为柄体(Handle and Body)模式或接口(Interface)模式. ...

  8. 诡异的防火墙故障,能PING通,但访问ORACLE不稳定

    上周四我们的IDC核心数据区到DMZ区的防火墙经历了十分诡异的故障. 虽然我从一开始就断定非ORACLE问题,因为连接核心数据区的多个ORACLE数据库的应用都无一例外的出现了相同的问题,如果三个以上 ...

  9. 使用Mahout搭建推荐系统之入门篇-Mahout实战

    原始地址:http://my.oschina.net/Cfreedom/blog/201828 用意: 结合上篇博客,写写代码熟悉一下Mahout.很多地方想法都比较粗糙,亟待指正.  代码放在了:  ...

最新文章

  1. ORB-SLAM2从理论到代码实现(四):相机成像原理、基本矩阵、本质矩阵、单应矩阵、三角测量详解
  2. wxWidgets随笔(9)-utf8~wxString存储二进制数据(4)
  3. OpenCV imgproc分割(segmentation)的实例(附完整代码)
  4. 【C++】 C++标准模板库(四) Set
  5. python统计提取数量_python中统计计数的几种方法和Counter的介绍
  6. [OJ] Wildcard Matching (Hard)
  7. vue --- 前端代理发送http请求
  8. 用条件运算符编写java程序_Java 编程入门课程丨第 8 单元:条件运算符和控制语句...
  9. 《菜菜的机器学习sklearn课堂》聚类算法Kmeans
  10. logrotate 命令切换linux系统日志
  11. 服务器虚拟机要怎么安装,服务器虚拟机怎么安装
  12. JAVA知识体系之分布式篇(四)——Kafka
  13. u盘数据恢复软件哪个好?怎么恢复u盘数据?
  14. 湖南师范大学2021年3月25日蓝桥杯热身赛解题报告与标程
  15. 模模搭古城搭建学习笔记3:建筑篇
  16. 苹果计算机关机时间不准,苹果电脑怎么设置定时自动关机
  17. 【自省篇】软件开发七宗罪
  18. Spring Boot的启动流程
  19. 叁-拾玖|c++入门笔记
  20. c++面试常见题·Part 2 数据结构和STL

热门文章

  1. 程序员频繁跳槽,将被列入失信名单!
  2. java 08_java08
  3. 微信登录——授权登录获取用户信息
  4. 红宝书系列之 var let const 的区别
  5. 污水处理新技术 MBR膜的应用优势
  6. HDMI 接口干扰WiFi 信号
  7. 【电子学会】2022年06月图形化二级 -- 大鱼吃小鱼
  8. 两数和(LeetCode)
  9. 记忆方法<一> 前言
  10. 动态修改imageview大小