Oh, that’s a good question, and although it’s a cliché, very few people actually know the solution. "Effective C++" has an introduction, and I recommend this book to all C++ers. I will briefly summarize it here.
In a large-scale project with organizational problems, the biggest problem affecting the compilation speed is that the header files form a huge dependency network. Modification of one of the header files will cause a large number of indirectly dependent source code files to be recompiled. a.h contains b.h, b.h contains c.h, and c.h contains d.h. Even if a.h and d.h seem to have nothing to do, it is inevitable that a.cc will be recompiled when you modify d.h.
First of all, you need to know a feature of C++. Everyone knows that functions are divided into two parts: declaration and implementation, but classes can also be divided into pre-declaration and definition. Maybe fewer people know it, and even more people know how to use it. If it is less, it can actually be used to solve the compilation speed problem.
class Object;//前置声明
class Object { //类定义
void Method();
};
void Object::Method() { //实现
}
These three parts can be written in three files, namely "filefwd.h" "file.h" "file.cc", the latter one includes the previous one. Among them, filefwd.h is called a "predeclaration file", and many open source projects are designed this way.
"Effective C++" summarizes that in many cases you do not need to include "file.h", you only need to include the forward declaration "filefwd.h":
Use a reference or pointer to a class, including as a smart pointer.
Use classes as return types.
You must include "file.h" in the following cases:
Use classes as member objects.
Use classes as base classes.
Use classes as function parameters.
Access member functions or objects of a class.
For example, I am now developing a function func whose implementation calls the Method of Object. I can write like this:
What’s the use of all this trouble? You will find that most .h files do not need to contain other .h files, but only need to contain fwd.h. Since fwd.h does not need to define members of the class, it has very few dependencies and rarely contains other files. And only .cc needs to contain a lot of .h.
So your code forms a concise header file dependency hierarchy, .cc depends on *.h, .h depends on fwd.h, instead of a lot of .h relying on each other, each modification of .h requires recompiling There are very few .cc files and the compilation speed is faster.
In addition, there is another technique that should be used in combination, which is the Impl design pattern mentioned by @spacewander. Its purpose is to move more dependencies to .cc files and reduce .h dependencies as much as possible. For example, if I want to design a class Object, I need to use vector as the internal implementation. Using impl can avoid including vector in .h.
filefwd.h The pre-declaration file mentioned earlier:
#ifndef INCLUDE_FILE_FWD_H
#define INCLUDE_FILE_FWD_H
class Object;
#endif
The combination of the two modes not only forms an elegant "separation of interface and implementation", but also makes it so refreshing during compilation. There is no need to worry about the additional runtime overhead caused by one more call. In fact, as long as all methods of the impl class are inlined (even the compiler will automatically add them), there will be no performance loss at all. The cost of this is that each module has a lot of interface code (look at the example above, almost twice as much), even more than the logic itself, and there are trade-offs before using it. My experience is: it’s worth it to make your code look more stylish and be admired by other programmers!
You can consider the Impl idiom in C++.
Put the function into an Impl class, and then this class holds a pointer to the Impl class, and the external interface is implemented by calling the corresponding method of the Impl class.
//Stack.h
class Stack{
public:
Stack();
~Stack();
public:
void push(int i);
int pop();
private:
class StackImpl;//StackImpl类声明
StackImpl *pStackImpl;
}
// StackImpl.h
class StackImpl{
public:
StackImpl();
~StackImpl();
public:
void push(int i);
int pop();
}
// Stack.cpp
#include "StackImpl.h"
void Stack::push(int i)
{
pStackImpl->push(i);
}
...
For this kind of class that is referenced by multiple classes, the stability of the interface should be ensured. That is, the implementation in the .cpp file can be greatly changed, but the .h file must remain stable, which requires the initial design of the class. You need to be thoughtful and flexible at all times, fully consider the functions that may need to be added later, design the interface first, and then implement them one by one. If you do not need it temporarily, you can reserve the interface without implementing it. If the initial consideration is not enough, during the project progress, when the amount of code is not so large that it is difficult to maintain, continue to refactor until the interface design of each commonly used class becomes reasonable and stable, and then expand on this basis. This is true for anything as small as a class, as large as a module, a component, or even an entire project. It is necessary to refactor the code as much as possible before it becomes too complex to maintain, and then expand it until the interface becomes reasonable and stable.
But after saying so much, it doesn’t seem to be of much help to your problem haha.
Oh, that’s a good question, and although it’s a cliché, very few people actually know the solution. "Effective C++" has an introduction, and I recommend this book to all C++ers. I will briefly summarize it here.
In a large-scale project with organizational problems, the biggest problem affecting the compilation speed is that the header files form a huge dependency network. Modification of one of the header files will cause a large number of indirectly dependent source code files to be recompiled. a.h contains b.h, b.h contains c.h, and c.h contains d.h. Even if a.h and d.h seem to have nothing to do, it is inevitable that a.cc will be recompiled when you modify d.h.
First of all, you need to know a feature of C++. Everyone knows that functions are divided into two parts: declaration and implementation, but classes can also be divided into pre-declaration and definition. Maybe fewer people know it, and even more people know how to use it. If it is less, it can actually be used to solve the compilation speed problem.
These three parts can be written in three files, namely "filefwd.h" "file.h" "file.cc", the latter one includes the previous one. Among them, filefwd.h is called a "predeclaration file", and many open source projects are designed this way.
"Effective C++" summarizes that in many cases you do not need to include "file.h", you only need to include the forward declaration "filefwd.h":
You must include "file.h" in the following cases:
For example, I am now developing a function func whose implementation calls the Method of Object. I can write like this:
func.h:
func.cc:
What’s the use of all this trouble? You will find that most .h files do not need to contain other .h files, but only need to contain fwd.h. Since fwd.h does not need to define members of the class, it has very few dependencies and rarely contains other files. And only .cc needs to contain a lot of .h.
So your code forms a concise header file dependency hierarchy, .cc depends on *.h, .h depends on fwd.h, instead of a lot of .h relying on each other, each modification of .h requires recompiling There are very few .cc files and the compilation speed is faster.
In addition, there is another technique that should be used in combination, which is the Impl design pattern mentioned by @spacewander. Its purpose is to move more dependencies to .cc files and reduce .h dependencies as much as possible. For example, if I want to design a class Object, I need to use vector as the internal implementation. Using impl can avoid including vector in .h.
filefwd.h The pre-declaration file mentioned earlier:
file.h:
file.cc:
The combination of the two modes not only forms an elegant "separation of interface and implementation", but also makes it so refreshing during compilation. There is no need to worry about the additional runtime overhead caused by one more call. In fact, as long as all methods of the impl class are inlined (even the compiler will automatically add them), there will be no performance loss at all. The cost of this is that each module has a lot of interface code (look at the example above, almost twice as much), even more than the logic itself, and there are trade-offs before using it. My experience is: it’s worth it to make your code look more stylish and be admired by other programmers!
You can consider the Impl idiom in C++.
Put the function into an Impl class, and then this class holds a pointer to the Impl class, and the external interface is implemented by calling the corresponding method of the Impl class.
For this kind of class that is referenced by multiple classes, the stability of the interface should be ensured. That is, the implementation in the .cpp file can be greatly changed, but the .h file must remain stable, which requires the initial design of the class. You need to be thoughtful and flexible at all times, fully consider the functions that may need to be added later, design the interface first, and then implement them one by one. If you do not need it temporarily, you can reserve the interface without implementing it. If the initial consideration is not enough, during the project progress, when the amount of code is not so large that it is difficult to maintain, continue to refactor until the interface design of each commonly used class becomes reasonable and stable, and then expand on this basis. This is true for anything as small as a class, as large as a module, a component, or even an entire project. It is necessary to refactor the code as much as possible before it becomes too complex to maintain, and then expand it until the interface becomes reasonable and stable.
But after saying so much, it doesn’t seem to be of much help to your problem haha.
In addition to keeping the interface stable, precompiling header files into gch files or pch files can also help speed up compilation.
For specific information, please refer to Wikipedia: https://en.wikipedia.org/wiki/Precompiled_header