C + + templates cannot be detached from compilation

Source: Internet
Author: User

c++template header files and defining issues for separate compilation

(1)

Foo.h
Template<typename t>
Class Foo
{
Public
void f ();
};

Foo.cpp
#include <iostream>
#include "Foo.h"

Template<typename t>
void Foo<t>::f ()
{
Std::cout << "foo<t>::f ()/n";
}

Main.cpp
#include "Foo.h"

int main ()
{
Foo<int> x;
X.F ();
}

As on the organization, an error will be compiled.

And if Foo.h and Foo.cpp are merged together, there will be no compilation errors.

#include <iostream>

Template<typename t>
Class Foo
{
Public
void f ();
};
Template<typename t>
void Foo<t>::f ()
{
Std::cout << "foo<t>::f ()/n";
}


int main ()
{
Foo<int> x;
X.F ();
}

That's right.

Why is it?

(2) Since there is no compiler that supports template separation and compilation (not currently), our common template header files (such as <vector>) put the Declaration and implementation together (rather than just putting the declaration into the header file, as we usually do).
This poses a problem, such as:

1.C:
#include <vector>
...

2.C:
#include <vector>
...

GCC 1.c 2.c

The question is why it can be passed (hehe, this and #ifdef | #define | #endif pre-treatment independent)?
According to the foregoing, the equivalent of 1.c and 2.c each have a vector of the implementation, should not be able to link through AH.

Oh, this time may be as you suspect, it is our compiler to do the work, the compiler in order to ensure that the template header file and other header files (including declarations only) have the same behavior, it will be the template for the corresponding covert processing, to ensure that the template is only implemented once.

(3)

In the (1) example, in order to allow the definition and declaration to be separated, the declaration is placed in the foo.h, and the definition is placed in the Foo.cpp, which is the same as the function of the non-template type. Unfortunately, this is not possible because the template type function cannot be compiled separately (because there is no export support). in order for the compiler to pass, the foo.cpp to include into the foo.h, which is actually the integration of two files into a file. There is no problem with compiling this, but be careful that you need to separate the foo.cpp from the project, because it cannot be compiled, or change its extension from. cpp to. hpp.

It is not recommended so separate writing, to tell the truth this is not what the standard practice, and this does not have much significance. If you want to separate, you can suffix the definition to a file after the declaration. But it does not make much sense to separate. You can read about how boost is doing.

-------------------------------------------

Why C + + The compiler cannot support detached compilation of templates

Liu Weipeng (Pongba)

The Louvre in C + + (HTTP://BLOG.CSDN.NET/PONGBA)

First, a compilation unit (translation unit) refers to a. cpp file and its # All the. h files in the include, the code in the. h file will be extended to the. cpp file containing it, and then the compiler compiles the. cpp file as an. obj file ( assuming our platform is Win32), The latter has a PE (portable executable, Windows executable) file format, and itself contains a binary code, but not necessarily able to execute, because there is no guarantee that there must be a main function. When the compiler compiles all the. cpp files in a project in a separate way, the connector (linker) connects to an. exe file.

As an example:

---------------Test.h-------------------//

void f ();//Declare a function f here

---------------Test.cpp--------------//

#include "test.h"

void F ()

{

...//do something

}//This implements the F function declared in Test.h

---------------main.cpp--------------//

#include "test.h"

int main ()

{

f (); Call F,f with an external connection type

}

In this example, test. CPP and main.cpp are each compiled into different. obj files (named Test.obj and Main.obj), and in main.cpp, the F function is called, but when the compiler compiles main.cpp, it only knows only one of the main.cpp files contained in test.h A declaration of Void F (), so the compiler considers f here as an external connection type, That is to say that its function implementation code in another. obj file, this example is test.obj, that is, Main.obj does not actually have a single line of binary code about the F function, and the code actually exists in the test.obj compiled by Test.cpp. The call to F in Main.obj will only generate a single row of calls, like this:

Call F [the name in C + + is, of course, handled by mangling[]

At compile time, this call instruction is obviously wrong, because there is no implementation code for a line F in main.obj. What about that? This is the task of the connector, which is responsible for finding the implementation code of F in the other. obj (in this case, test.obj), and finding the function entry point address of the call F, which is the calling address of the instruction, to the actual F. It is important to note that the connector actually "connects" the. obj in the project into an. exe file, and its most critical task is to look for an external connection symbol in another. obj address, and then replace the original "false" address.

This process, if it's more thorough, is:

Call F This line of command is actually not the case, it is actually called stub, that is, a jmp 0xABCDEF. This address may be arbitrary, but the key is that there is a single line of instructions on the address to perform the real call F action. In other words, all calls to f in this. obj file are in jmp to the same address, where it is really "call" F. The advantage of this is that the connector modifies the address as long as it changes the call XXX address of the latter. However, how does the connector find the actual address of F (in this case this is in test.obj) because. obj is the same format as the. exe, in which there is a symbol import table and a Symbol export table (import table and exported Table) where all the symbols are associated with their addresses. So the connector simply looks for the address of the symbol f (c + + f mangling) in the Test.obj's symbolic export table, Then do some offset processing (because the two. obj files are merged, of course the address will have a certain offset, this connector is clear) to write to the symbol in the Main.obj table in the form of the one that the F occupies.

This is the approximate process. The key is:

When compiling main.cpp, the compiler does not know the implementation of F, so when it encounters a call to it it simply gives an indication that the connector should look for the implementation of F for it. This means that there is no line of binary code for f in Main.obj.

When compiling test.cpp, the compiler found the implementation of F. The implementation of the F (binary code) appears in the Test.obj.

When connected, the connector finds the address of the implementation code (binary) of F in test.obj (export table by symbol). Then change the pending call XXX address in main.obj to the actual address of F. Complete.

However, for templates , you know , the code of a template function does not actually compile directly into binary code, where there is an "instantiation" of the process. As an example:

----------main.cpp------//

Template<class t>

void f (T t)

{}

int main ()

{

...//do something

f (10); The call F<int> compiler here decides to give an instance of f a f<int>

...//do other thing

}

That is, if you have not called F,f in the main.cpp file, you will not be instantiated , and thus there is no line of binary code for F in Main.obj! If you call it this way:

f (10); F<int> can be instantiated.

f (10.0); F<double> can be instantiated.

In this way, Main.obj also has a binary code snippet that f<int>,f<double> two functions. And so on

Instantiation, however, requires the compiler to know the definition of a template, doesn't it?

Take a look at the following example (separating the Declaration and implementation of the template):

-------------test.h----------------//

Template<class t>

Class A

{

Public

void f (); This is just a statement.

};

---------------test.cpp-------------//

#include "test.h"

Template<class t>

void A<t>::f ()//Template implementation

{

...//do something

}

---------------main.cpp---------------//

#include "test.h"

int main ()

{

A<int> A;

f (); #1

}

The compiler did not know the definition of a<int>::f in the # #, because it was not in test.h, so the compiler had to send a hope to the connector that it could find an instance of A<int>::f in the other. obj. In this case, is test.obj, however, does the latter really have a<int>::f binary code? No! !! Because the C + + standard clearly indicates that when a template is not used, it should not be instantiated , Test.cpp used to a<int>::f it? No!! So actually Test.cpp compiled out of the Test.obj file about the A::f line of binary code is not, so the connector is dumbfounded, had to give a connection error. However, if you write a function in test.cpp that calls A<int>::f, the compiler instantiates it, Because at this point (test.cpp), the compiler knows the definition of the template, so it is able to instantiate, so the Test.obj symbol Export table has the address of the a<int>::f symbol, so the connector can complete the task.

The key is that, in a detached compilation environment, the compiler compiles one. cpp file without knowing the existence of another. cpp file and does not look for it (it will send the desired connector when a pending symbol is encountered). This mode works well without a template, but it is dumbfounded when it encounters a template, because the template is instantiated only when it is needed, so when the compiler sees only the declaration of the template, it cannot instantiate the template. You can create only one symbol with an external connection and expect the connector to have the address of the symbol resolved. However, when an instance of the template is not used in the. cpp file that implements the template, the compiler is lazy to instantiate it, so the entire project's. obj cannot find the binary code of the one-line template instance, so the connector is out of the way.

---------------------------------------------

There are two ways to do this.
1, directly put function declaration and function implementation all in the same. h file. When you refer to the. h file in this way, both the Declaration and the implementation are directly expanded in the instantiated. cpp file
This allows you to find the entry address of the function when compiling.
2, function declaration and function implementation are implemented separately. Where you instantiate the template, include "Xxx.cpp", which contains the. cpp file directly, so you can also find the function entry. As I have tested below.


In fact, I have seen the general article before that, if the Declaration and implementation are placed in the same. h file, in different. cpp files with different parameters to implement the template will be
Repeat the error defined by the report.

C + + templates cannot be detached from compilation

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.