Hello again!
In this video, we are going to look at extern templates. Wxtern templates are a useful way of addressing
the problem of "template bloat".
So let's find out what that involves.
When we have a template, we usually put the full template definition in a header.
If we have a template function, we would put the function body. And if we have a template class, then
we would put the full class definition.
This is different from the way that we normally handle functions and classes.
And the reason is, when we use the template in a source code file, we want the compiler to be able
to instantiate the template for us, when it is used.
And to do this, the compiler has to be able to see the full template definition.
If we have the same instantiation in lots of files.
So we have a file where we call the function with a string argument.
So the compiler will instantiate this with string as the parameter.
And then we have lots and lots of other files where this happens.
The compiler will actually instantiate the template separately, in every single file.
So we end up with hundreds of source code files, which contain the same instantiation code.
And you might think that is rather stupid. Why does the compiler not know that
it has already done that instantiation?
In fact, people have tried to write compilers which do keep track of this. And they only instantiate
when necessary.
But it is actually very, very hard to get it right. Dealing with all the unusual cases. And also getting
the correct code into the executable, and making sure it works with debuggers and other tools.
And in fact, it is just too difficult.
So compiler writers have tried this. But they have always given up and gone back to the crude but effective
way, of always instantiating regardless.
So we have 269 source code files which include the template instantiation code.
When we convert those to binary, we will have 269 object files which include the binary code for
the string instantiation.
That's not actually going to be a problem for creating the program binary, because the linker will
remove the duplicates.
It will just pick one of these and ignore all the rest.
So we only have one implementation code in the actual binary program executable.
However, this does increase the size of the object file because we have all this unnecessary code.
It also increases compilation time because the compiler is doing all this unnecessary work. And also
these unnecessarily large object files take longer to process, and they use more memory.
And this can be a serious problem in large projects, which take a long time to build.
The traditional way of doing this, is to manually instantiate the templates.
So in the header we only put the template declaration without the actual body of the function, or the
class definition.
We are also going to use another function which is not a template function.
I've just put the definition in here, for convenience.
Then we have our source code file which defines this function.
This will actually call the template function.
It includes the header, which just has the declaration of the template function.
So at this point, the compiler cannot see the definition, which means the compiler cannot instantiate
the function in this source code.
So we do not have any instantiation here.
And then in one, and only one source code, we provide the template definition.
So that means that in this source code file, the compiler will be able to instantiate the template,
and then we provide a manual instantiation.
So this is the instantiation which takes a string argument.
We need to put the template keyword in front, so the compiler knows that it is a template instantiation
and not just some non-template function.
And then when we call the function in here, this will use the instantiation.
And when we call the function which calls the template, then that will call that this instantiation
here.
So we only have one instantiation of the template in the entire program.
There we are.
So that is possible.
But it does mean a lot of work.
The programmers need to keep track of which templates they need to instantiate and where to instantiate them.
And in large projects, that can become a bit of a headache.
So it is not really all that useful.
Although, before C++11, this was the only way to do it.
Before we get on to extern templates, let's look at extern variables.
These were inherited from C.
They were used to make global variables accessible across different source files, which is a very bad idea.
This is why object oriented programming was invented,
in fact, because global variables were causing so many problems.
So we can declare a variable as extern, in a source file or in a header.
And this means that the actual variable is defined somewhere else.
So this is just a declaration of the variable.
And then we can use this in different source code files.
And then there has to be exactly one file where the variable is actually defined.
So we have the variable without extern. And also, this must not be initialized.
So this will actually define the meaning of life variable and the ones in the other source code are,
if you like, references to this one. Not the references that we have in C++, but a sort of
"link-time" reference.
So here is our header.
We have the meaning_of_life, which is extern.
So this means it is defined somewhere else.
Then we have our function
prototype again.
We have our function which uses this meaning_of_life variable.
It includes the header.
So this will be an extern variable.
So this function will know that the variable is defined somewhere else.
And then with our main function. Again, this will use the variable which is defined somewhere else.
And finally, we have the "somewhere else".
So here it is. This is where the meaning_of_life is actually defined.
And we are not allowed to initialize it here.
So the function will initialize it, or set its value to 42, and then the main() function will print
it out.
And this all works.
In C++11,
we can also use the extern keyword with templates.
We write out the template definition as usual, with the function body or the class definition.
And then, after that, we have an instantiation which is declared as extern.
So this will tell the compiler that there is an instantiation, somewhere else, in the source code for
the program.
So then when we have our files which call the function with a string argument, the compiler will
not instantiate the function in these files, because it knows that the instantiation is somewhere else.
And then we have one file, where the function is explicitly instantiated, without the extern keyword.
And then the compiler will that generates the instantiation that.
So we only have one instantiation in the entire program.
So here is our code.
We have our template function with the function body.
Then we have an instantiation which is declared as extern.
So any source files which include this header will know that the instantiation is done somewhere else.
Then we have our function which calls this instantiation, which is done somewhere else.
We have our main() function which calls the instantiation, which is done somewhere else.
And then we have the implementation file, where the instantiation is done explicitly.
So this will be where the template is instantiated, and this will contain the only copy of the source
code for the template body.
And that compiles.
So just to show the difference this makes, I had a little experiment.
So these are the sizes of the object files without extern.
And this is exactly the same code, but using extern templates.
So the object files are about 25% smaller.
And also the time taken would have been a lot shorter, because, in these ones, the compiler would have
to generate the instantiation in every source file.
And in these ones, it would only have to do it once.
And if you have hundreds of these files, then that would obviously save a lot of time.
And finally, this also works the same way with class templates.
So we puts the full template class definition in the header.
Then we have an extern instantiation.
So Test with int parameter will be instantiated somewhere else.
Then we have all these files which use the extern instantiation, and then we have an implementation
file, where the instantiation takes place.
Okay, so that is it for this video.
I will see you next time.
But until then, keep coding!