C++面试题:实现Pimpl模式时特殊成员函数需定义在实现里

alt

定义:Pimpl模式是指pointer to implementation。也就是说将类Widget的实现完全放到另外一个类Impl里,而类Widget对外提供接口,这些接口的调用最终会通过Impl指针成员(裸指针或智能指针),调用相应的实现接口。

优势:这么做可以减少项目的依赖,进而减少不必要的编译。

一、使用裸指针实现Pimpl模式

// widget.h
class Widget {
public:
  Widget();
  ~Widget();
private:
  struct Impl;
  Impl *pImpl;
};

// widget.cpp
#include "widget.h"
#include "gadget.h"
#include <string>
#include <vector>
struct Widget::Impl {
  std::string name;
  std::vector<double> data;
  Gadget g1, g2, g3;
};
Widget::Widget() 
  :pImpl(new Impl) {}
Widget::~Widget() {
  delete pImpl;
}

假设widget.h会被100个cpp文件调用,当gadget.h变化时,编译器只会编译一个widget.cpp文件。如果没有采用Pimpl模式模式,那么最终编译器会编译101次。由此可见,Pimpl模式能减少编译的次数,进而加快编译速度!

但是,正如你看到的,你需要在构造函数和析构函数里操作(new和delete)裸指针。有没有更好的办法来构建和销毁Impl对象呢?答案是使用智能指针,首先我们看看使用unique_ptr会遇到的问题。

二、使用std::unique_ptr实现Pimpl模式

将以上例子的Impl*简单替换成std::unique_ptr,如下所示:

// widget.h
class Widget {
public:
  Widget();
private:
  struct Impl;
  std::unique_ptr<Impl> pImpl;
};

// widget.cpp
#include "widget.h"
#include "gadget.h"
#include <string>
#include <vector>
struct Widget::Impl {
  std::string name;
  std::vector<double> data;
  Gadget g1, g2, g3;
};
Widget::Widget() 
  :pImpl(std::make_unique<Impl>()) {}
}

// client.cpp
#include "widget.h"
Widget w; // 这里会报错!!

为什么在client.cpp里的调用会报错?根本原因在于std::unique_ptr的析构函数里调用了static_assert,该函数会在编译器间判断Impl的定义是否可见,而在client.cpp里,编译器只看到了Impl的前置声明,而没有完整的定义(完整的定义在widget.cpp里),因此编译器会报这种错误:在非完整型别上实施了sizeof或者delete。之所以会调用std::unique_ptr的析构函数,是因为编译器会自动生成Widget w的析构调用。

为了解决这个问题,我们需要把Widget的析构函数放到一个可以看得见Impl定义的地方,也就是widget.cpp。然而,当你在widget.cpp中定义Widget的析构函数时,编译器不会自动生成Widget的move操作,因此,你还需要在widget.cpp中定义Widget的move操作,如下所示:

// widget.h
class Widget {
public:
  Widget();
  ~Widget();
  Widget(Widget&& rhs); // 仅声明
  Widget operator=(Widget&& rhs);  // 仅声明
private:
  struct Impl;
  std::unique_ptr<Impl> pImpl;
};

// widget.cpp
Widget::Widget() 
  :pImpl(std::make_unique<Impl>()) {}
}
Widget::~Widget() = default;
Widget(Widget&& rhs) = default; // 注意!!
Widget operator=(Widget&& rhs) = default;

这里需要注意的是,把move操作的定义放到widget.h里也会出现同样的编译错误,为什么?原因有2个:1)当编译器遇到Widget(Widget&& rhs) = default,它会自动生成异常处理逻辑,而这段逻辑里会调用Widget的析构函数;2)当编译器遇到Widget operator=(Widget&& rhs) = default,它会自动生成一段代码:调用std::unique_ptr的析构函数来销毁pImpl,然后才完成赋值。

最后,由于std::unique_ptr是一个移动拷贝类型,因此,当你使用std::unique_ptr实现Pimpl模式时,需要实现深拷贝的copy操作。

三、使用std::shared_ptr实现Pimpl模式

std::shared_ptr实现Pimpl模式就容易多了。因为shared_ptr无需看见Impl的定义,所以编译器为Widget生成的析构函数就足够了。因为不需要手动定义Widget析构函数,因此编译器也会自动为Widget生成对应的移动操作。实现的细节如下:

// widget.h
class Widget {
public:
  Widget();
private:
  struct Impl;
  std::shared_ptr<Impl> pImpl;
};

// widget.cpp
Widget::Widget() 
  :pImpl(std::make_shared<Impl>()) {}
}

// client.cpp
Widget w1;
auto w2(std::move(w1)); // 会自动生成移动构造函数,所以这里是移动!!

为什么std::unique_ptr和std::shared_ptr在实现Pimpl会有如此大的区别?

因为,std::unique_ptr的delete操作是std::unique_ptr的一部分,而std::shared_ptr的delete操作不是std::shared_ptr类型的一部分。

聊完以上知识点,感觉自己的C++技能又提升了一点。希望以上剖析的思路,能有效地帮助你学习以下经典C++书籍。

alt

#C++##C++面试题##面试#
全部评论

相关推荐

2025-11-27 01:09
电子科技大学 C++
牛客68151836...:实习不相关就靠后写吧,因为大概面试官也不感兴趣。前面区域写一点更容易引起提问的内容,比如投后台就把服务器项目提前。
简历上的经历如何包装
点赞 评论 收藏
分享
bg双非本科,方向是嵌入式。这次秋招一共拿到了&nbsp;8&nbsp;个&nbsp;offer,最高年包&nbsp;40w,中间也有一段在海康的实习经历,还有几次国家级竞赛。写这篇不是想证明什么,只是想把自己走过的这条路,尽量讲清楚一点,给同样背景的人一个参考。一、我一开始也很迷茫刚决定走嵌入式的时候,其实并没有一个特别清晰的规划。网上的信息很零散,有人说一定要懂底层,有人说项目更重要,也有人建议直接转方向。很多时候都是在怀疑:1.自己这种背景到底有没有机会2.现在学的东西到底有没有用3.是不是已经开始晚了这些问题,我当时一个都没答案。二、现在回头看,我主要做对了这几件事第一,方向尽早确定,但不把自己锁死。我比较早就确定了嵌入式这个大方向,但具体做哪一块,是在项目、竞赛和实习中慢慢调整的,而不是一开始就给自己下结论。第二,用项目和竞赛去“证明能力”,而不是堆技术名词。我不会刻意追求学得多全面,而是确保自己参与的每个项目,都能讲清楚:我负责了什么、遇到了什么问题、最后是怎么解决的。第三,尽早接触真实的工程环境。在海康实习的那段时间,对我触动挺大的。我开始意识到,企业更看重的是代码结构、逻辑清晰度,以及你能不能把事情说清楚,而不只是会不会某个知识点。第四,把秋招当成一个需要长期迭代的过程。简历不是一次写完的,面试表现也不是一次就到位的。我会在每次面试后复盘哪些问题没答好,再针对性补。三、我踩过的一些坑现在看也挺典型的:1.一开始在底层细节上纠结太久,投入产出比不高2.做过项目,但前期不会总结,导致面试表达吃亏3.早期有点害怕面试,准备不充分就去投这些弯路走过之后,才慢慢找到节奏。四、给和我背景相似的人一点建议如果你也是双非,准备走嵌入式,我觉得有几件事挺重要的:1.不用等“准备得差不多了”再投2.项目一定要能讲清楚,而不是做完就算3.不要只盯着技术,多关注表达和逻辑很多时候,差的不是能力,而是呈现方式。五、写在最后这篇总结不是标准答案,只是我个人的一次复盘。后面我会陆续把自己在嵌入式学习、竞赛、实习和秋招中的一些真实经验拆开来讲,希望能对后来的人有点帮助。如果你正好也在这条路上,希望你能少走一点弯路。
x_y_z1:蹲个后续
点赞 评论 收藏
分享
评论
2
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务