这个其实书里面已经说的很清楚了。
虽然各个编译器有自己的 trick, C++ 的文件依赖实现基本来说非常的简单。 如果你写一个
#include "person.h"
预编译器真的就是把这个文件拼接在了 #include 那里..
我们假设 person.h 里面包含
class Person { public: std::string name() const; ... private: std::string mName; }
而 main.cpp 依赖于 person.h。 那么基本上,不管你是怎么改 person.h, 甚至就算是 touch 了一下, 大部分依赖管理器也会让编译器重新编译一次,区别可能仅仅是好一点的编译器很快发现其实 person.h 根本没有变化, 编译时间稍微短一些而已。
那么问题来了, 我们现在看到 main.cpp -> person.h, 意味着任何时候 person.h 的改变都会导致 main.cpp 重新被编译。在现实情况中, 甚至会有上百的个文件都会依赖于 person.h, 我们并不想因为 person.h 被修改就导致所有依赖于它的文件被编译,那么怎么做呢。 书里面说了一个小 trick, 把类和类的具体实现分开 - pimpl idom (PIMPL, Rule of Zero and Scott Meyers).
基本上是把这个 Person 类拆开
class Person { public: std::string name() const; ... private: class PersonImpl * pImpl; }
in Person.cpp
#include "personimpl.h" std::string Person::name() { return pImpl->name(); }
PersonImpl 作为一个具体的实现类
class PersonImpl { public: std::string name() const { return mName; } ... private: std::string mName; }
这里你可以看到, person.h 里面只是包含了接口信息,具体的实现挪到了PersonImpl 这个类。 由于 Person 对 PersonImpl 的引用是一个指针, 而指针大小在同一平台是固定的。 所以 person.h 根本就不需要包含 personimp.h ( 注意 person.cpp 需要包含 personimpl.h, 因为需要具体使用到 PersonImpl 的函数)。
于是文件关系依赖改变为:
main.cpp -> person.h person.cpp -> person.h, personimpl.h personimpl.cpp -> personimpl.h
所以你看到, main.cpp 和 personimpl.h 彻底解除了依赖关系。如果还有一百个文件依赖于 person.h, 而你又想改 Person 的实现。 由于 Person 的实现在 personimpl.cpp/h 里面, 不管你怎么去搞, 由于你压根就不会去碰 person.h , 所有的依赖 person.h 文件都不会被重新编。
说了怎么多好处, 那么这里给题主提几个问题思考下:
- 你真的想把你的实现藏在另外一个文件嘛, 你确定看代码的人看到这种一层套一层的实现到处找你的代码的时候不会想砍死你。。
- 这个依赖关系又会把编译时间降低多少呢?如果仅仅是几个文件依赖这个类定义,多搞一个类出来是否值得?
- 说到把接口和实现分开, 你肯定在想, 尼玛, 我直接把 Person 搞成一个接口不就行了嘛。 为啥还要这么麻烦。
=========
如果你思考过这些问题,说明你已经摆脱了教科书里面这些条条款款, 进入真正的工程实践领域了。
这种降低文件依赖关系的做法一般并不会在开始写代码的时候做, 这个叫 Premature optimization, 因为你可能在优化一个根本就不存在的问题。
在大工程里面, 把代码写得清晰,容易懂, 比什么优化都重要, 而如果不是大工程, 编译时间本来也就不长。
而你代码写好了以后,如果发现很多文件依赖一个类实现,再把这个类改成 pimpl idom 也不迟。
甚至关于优化编译时间, 也有非常多的技巧, 比如, 另外一个极端是,就算你不想拆代码, 构造一个新的 cpp, 把其他所有的 cpp 全部包含进去。
all.cpp
#include "person.cpp" #include "personimpl.cpp" #include "main.cpp"
依赖关系不是复杂难搞嘛, 我全部包含在一起,虽然不管改个啥都要重新编,但是只用编一个 cpp 啊,不管编译还是链接都要快几个数量级。 ( opera 用了这个 trick 编译时间从 半个小时降到了 5分钟。。)
对了,对于最后一个问题,接口 + 实现的最大问题是接口本身不能被直接构建出来, 所以
- 没法在栈上面用
- 需要一个辅助的工厂类