Here we will talk about the compilation process of C ++ files and the template compilation process;
I. General C ++ application compilation process.
Generally, the compilation process of C ++ applications is divided into three stages. The same is true for templates.
Expand the include file in the cpp file.
Compile each cpp file into a corresponding obj file.
Connects the obj file to an exe file (or other library files ).
The following sections describe these stages respectively.
1. Expand the include file.
The expansion of the include file is a simple process. It only copies the Code contained in the include file to the cpp file (or other header files) that contains the file. The expanded cpp file becomes an independent compilation unit. In some articles, I see that the. h file and. cpp file are considered as a compilation unit. I think there is a problem with this understanding. As for the reason, let's take a look at the following notes.
1): The. h file that is not included in any other cpp file or header file will not be compiled. And will not eventually become part of the application. Let's take a look at a simple example:
1 =========== test. h file ================
2 // note that there is no semicolon. That is to say, if compilation is performed, errors will be generated here.
3 void foo () Add a test. h file in your application, as shown above. However, do not include this file in any other files. After compiling the C ++ project, you will find that the above Code error is not reported. This indicates that the H file itself is not a compilation unit. Only when the include statement is finally included in A. cpp file will it become a compilation unit.
2): There is a possibility that a cpp file directly or indirectly contains the same. h file multiple times. The following is a situation:
1 // ============== test. h ==================
2 // define a variable
3 int I;
4
5 // ============== test1.h ================
6 // contains the test. h file
7 # include "test. h"
8
9 // ============= main. cpp ============
10 // both test. h and test1.h are included,
11 // that is, two variables I are defined at the same time.
12 // a compilation error will occur.
13 # include "stdafx. h"
14 # include "test. h"
15 # include "test1.h"
16
17 void foo ();
18 void foo ();
19
20 int _ tmain (int argc, _ TCHAR * argv [])
21 {
22 return 0;
23}
After the code above is expanded, it is equivalent to defining two variables I in main. cpp at the same time. Therefore, a compilation error occurs. The solution is to use the # ifndef or # pragma once macro so that test. h can only be included once in main. cpp. For more information about # ifndef and # pragma once, see here.
3): Note that the include file is expanded to the cpp file in the defined order. For more information, see the following example.
1 // ============== test. h ==================
2 // declare a function. Note that there is no semicolon.
3 void foo ()
4
5 // ============== test1.h ================
6 // only one Semicolon is written.
7;
8
9 // ============= main. cpp ============
10 // note that the header file is included in the order of test. h and test1.h.
11 # include "stdafx. h"
12 # include "test. h"
13 # include "test1.h"
14
15 int _ tmain (int argc, _ TCHAR * argv [])
16 {
17 return 0;
18}
If you look at the above Code separately, a semicolon is required after test. h to compile the code. The semicolon defined in test1.h can be used to add the semicolon after test. h. Therefore, the sequence definition such as "security" can be compiled normally after being defined in main. cpp. Although this is not recommended in actual projects, this example shows a lot about the content contained in the file.
Some people may see that although a function is declared in the above example, it is not implemented and can still be compiled. This is the content of the following cpp file during compilation.
2. Compile and link the CPP file.
As we all know, C ++ compilation is actually divided into two phases: Compilation and linking, because these two phases are closely linked. So put them together. During compilation, the compiler generates an obj file for each cpp file. The obj file is in the format of PE [Portable Executable, that is, windows Executable File], and contains Binary Code. However, it cannot be executed, because it is not guaranteed that there must be a main function. After all cpp files are compiled, the obj file will be linked to an exe file (or other libraries) as needed ). See the following code:
1 // ================ test. h ==========================
2 // declare a function.
3 void foo ();
4
5 // ================ test. cpp ====================
6 # include "stdafx. h"
7 # include <iostream>
8 # include "test. h"
9
10 // implement the function defined in test. h.
11 void foo ()
12 {
13 std: cout <"foo function in test has been called." <std: endl;
14}
15
16 // =============== main. cpp ==================
17 # include "stdafx. h"
18 # include "test. h"
19
20 int _ tmain (int argc, _ TCHAR * argv [])
21 {
22 foo ();
23
24 return 0;
25}
Note that 22 rows call the foo function. The actual operation process of the above Code is that the compiler first generates an obj for each cpp file. Here is test. obj and main. obj (there is also an stdafx. obj, because the VS editor is used ). But there is a problem here, although test. h for main. cpp is visible (main. cpp contains test. h), but test. cpp for main. cpp is not visible, so main. how does cpp find the implementation of the foo function? In fact, when compiling the main. cpp file separately, the compiler does not focus on whether or where the foo function has been implemented. It only regards it as an external link type, and thinks that the implementation of the foo function should be in another obj file. When Row 22 calls foo, the compiler only uses an address jump, such as jump 0x23423. However, I don't know where foo exists, so I just entered a fake address after jump (please advise me what it should be ). Then compile the following code. After all cpp files are executed, they enter the link stage. The format of .objand .exe is the same. In such a file, there is a symbolic import table and a symbolic export table [import table and export table] in which all symbols are associated with their addresses. In this way, the connector only needs to be in the test. find the address of the symbol foo [of course C ++ mapping] For foo In the obj symbol export table, and then perform some offset processing. obj file merge, of course, the address will be offset, this connector is clear] write into main. the one that foo occupies in the symbol import table in obj. In this way, foo can be successfully executed.
Compile main. when cpp is used, the compiler does not know the implementation of f. All calls to it only give an indication that the connector should look for the implementation body of f for it. That is to say, main. obj does not have any line of binary code about f. When compiling test. cpp, the compiler finds the implementation of f. The [binary code] Implementation of foo appears in test. obj. During connection, the connector finds the address of the foo implementation code [binary] [export table by symbol] In test. obj. Then, change the pending jump XXX address in main. obj to the actual address of foo.
Now let's assume that the implementation of foo () does not actually exist? Let's take a look at the following code:
1 # include "stdafx. h"
2 // # include "test. h"
3
4 void foo ();
5
6 int _ tmain (int argc, _ TCHAR * argv [])
7 {
8 foo ();
9
10 return 0;
11}
Note that the above Code has commented out # include "test. h" and declared a new foo function. Of course, you can also directly use the function declaration in test. h. The above Code has no function implementation. According to the above analysis, the compiler will not report errors when it finds the call to foo (), but expects the connector to find the foo implementation in other obj files. However, the connector is still not found. A link error is reported.
LINK: E: \ CPP \ CPPTemplate \ Debug \ CPPTemplate.exe is not found or it is not generated in the previous incremental LINK;
Let's look at the following example:
1 # include "stdafx. h"
2 // # include "test. h"
3
4 void foo ();
5
6 int _ tmain (int argc, _ TCHAR * argv [])
7 {
8 // foo ();
9
10 return 0;
11}
Here there is only the foo Declaration. We have removed the original foo call. The above code can be compiled. The reason is that the foo function, main. cpp does not actually find the foo implementation (main. obj internal or main. obj external), the compiler will not care if foo has been implemented.
Ii. template compilation process.
After understanding the compilation process of the C ++ program, let's look at the template compilation process. As you know, a template must be instantiated into a specific class or function by template parameters before it can be used. However, the call of a class template member function has a very important feature, that is, the member function is initialized only when called. Because of this feature, the code of the class template cannot be organized according to the conventional C ++ class. Let's take a look at the following code:
1 // =========== testTemplate. h ====================
2 template <typename T>
3 class MyClass {
4 public:
5 void printValue (T value );
6 };
7
8 // =========== testTemplate. cpp ================
9 # include "stdafx. h"
10 # include "testTemplate. h"
11
12 template <typename T>
13 void MyClass <T >:: printValue (T value)
14 {
15 //
16}
The content of main. cpp is as follows:
1 # include <iostream>
2 # include "testTemplate. h"
3
4 int main ()
5 {
6 // 1: instantiate a class template.
7 // MyClass <int> myClass;
8
9 // 2: Call the member functions of the class template.
10 // myClass. printValue (2 );
11
12 std: cout <"Hello world! "<Std: endl;
13 return 0;
14}
Note the two comments. We will follow the steps to describe the template compilation process.
1): remove the testTemplate. cpp file from the project to delete the definition of testTemplate. cpp. Then directly compile the above files and the files can be compiled successfully. This indicates that the compiler did not check the implementation of the template class when compiling the main. cpp file after expanding testTemplate. h. It just remembers that there is such a template declaration. Since the template member function is not called, the compiler link phase will not find the implementation code of the class template in other obj files. Therefore, the above Code is correct.
2): remove the annotator of Line 1 in the main. cpp file. That is, the instantiation code of adding a class template. During project compilation, you will find that the project can also be compiled. In retrospect, testTemplate. h is expanded. That is to say, the main. cpp Statement of MyClass <T> can be found during compilation. Then, a class template can be instantiated normally during compilation of 7th rows. Note: The member functions of the class template will be instantiated only when called. Therefore, the compiler does not look for the implementation code of the class template because there is no call to the class template member function. Therefore, the above functions can be compiled.
3): remove the code annotator of the above 10th lines. That is, the call to the class template member function is added. After compilation, a link error is prompted. The implementation of printValue cannot be found. The truth is that there is only a function declaration above, and no function implementation is the same. That is, the compiler is compiling main. when cpp row 10th was found on myClass. printValue is called. In this case, no specific implementation can be found in the current file. Therefore, a flag will be made to wait for the linker to find the function implementation in other obj files. Similarly, the connector cannot find an obj file that contains the MyClass <T>: PrintValue declaration. Therefore, a link error is reported.
4): Since the testTemplate. cpp file cannot be found, we will include the testTemplate. cpp file in the project. Compile again. A link error is prompted in VS, indicating that the external type _ thiscall MyClass <int>: PrintValue (int) cannot be found ). You may find it strange that we have included the testTemplate. cpp file in the project. Let's first consider a problem. We have said that template compilation is actually an instantiation process, and it does not compile and generate binary code. In addition, the template member functions are initialized only when called. In the testTemplate. cpp file, because the testTemplate. h header file is included, this is an independent class template that can be compiled. However, the compiler does not instantiate the PrintValue member because no member function is called when compiling the testTemplate. cpp file. Maybe you will say that we have called the PrintValue function in main. cpp. But you need to know that testTemplate. cpp and main. cpp are two independent compilation units, and they do not know each other's behavior. Therefore, testTemplate. cpp only compiles content in testTemplate. h during compilation, that is, it declares the template again and does not instantiate the PrintValue member. Therefore, when main. cpp finds that the PrintValue member is required and finds it in testTemplate. obj, the target function cannot be found. To send a link error.
5): The template Code cannot be organized according to the conventional C/C ++ code. You must ensure that the template code can be found during compilation by using the template function to instantiate the template. There are many articles about this on the Internet. Template compilation is divided into including compilation and separate compilation. In fact, both including compilation and separate compilation are aimed at the same purpose: the template implementation code can be found during template instantiation. You can refer to this article.
Finally, let's make a small summary. The compilation of C ++ applications generally involves three phases: Expand the header file, compile the cpp file, and link. If an external type is required during compilation, the compiler will mark it and leave it to the connector for processing. If the connector cannot find the required external type, a link error occurs. For a template, the code of a separate template cannot be correctly compiled. You need an instantiate to generate a template instance before compiling. Therefore, you must ensure that the template code is visible at the place where the template is instantiated.