百科问答小站 logo
百科问答小站 font logo



如何理解《Effective C++》第31条将文件间的编译依赖关系降低的方法? 第1页

  

user avatar   fredriklo 网友的相关建议: 
      

这个其实书里面已经说的很清楚了。

虽然各个编译器有自己的 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分钟。。)

对了,对于最后一个问题,接口 + 实现的最大问题是接口本身不能被直接构建出来, 所以

- 没法在栈上面用

- 需要一个辅助的工厂类




  

相关话题

  如果编译器遇到未定义行为把硬盘格式化了,需要负法律责任吗? 
  指针是如何记住步长的? 
  为什么C++中virtual要翻译为虚函数? 
  很多人说 C++ 的 MFC 已经过时了,那新入门的人到底应该学什么? 
  字符串转化成整数,为啥我觉得我写的没有毛病呢? 
  为什么同为系统级编程语言,Rust 能拥有现代构建/包管理工具,C++ 却不能? 
  C 与 C++ 谁的效率高,为什么? 
  有哪些轻量级适合阅读的优秀 C++ 开源项目? 
  Matlab/NumPy/C++Eigen 速度差距为什么很大? 
  刷 LeetCode 对于国内 IT 企业面试帮助大吗? 

前一个讨论
小米宣布开始造车会对五菱汽车带来冲击吗?小米的优势会体现在哪些方面?
下一个讨论
中国税务局和美国国税局在权力上的差别如何?





© 2024-06-21 - tinynew.org. All Rights Reserved.
© 2024-06-21 - tinynew.org. 保留所有权利