[ ... Templates Adapted from

advertisement
[
Templates
]
Adapted from http://www.cs.washington.edu/people/acm/tutorials
The immortal question: Why?
What exactly are templates for, and why learn them?
• Limited Generic Programming (polymorphism)
Some functions have the same semantics for some (if not all) data
types. For instance, a function print() should display a sensible
representation of anything passed in. Ideally, it shouldn’t need to be
rewritten for each possible type.
• Less repetitive code
Code that only differs in the data type it handles does not have to be
rewritten for each and every data type you want to handle. It’s easier to
read and maintain since one piece of code is used for everything
Example:
a swap function
Problem: Oftentimes, it is nice to be able to swap the values of two
variables. This function’s behavior is similar for all data types. Templated
functions let you do that – in most cases without any syntax changes.
Stupid method – write an overloaded function for each type
Swap for integers
void swap(int &a, int &b) {
int c = a;
a = b;
b = c;
}
Swap for an arbitrary type T
void swap(T &a, T &b) {
T c = a;
a = b;
b = c;
}
Template method – write one templated function
template <typename T>
void swap(T &a, T &b) {
T c = a;
a = b;
b = c;
}
This function can be used with any
type that supports assignment and can
be passed in as a non-const reference.
Template Syntax:
swap dissected
The template<…> line states that
everything in the following
declaration or definition is under
the subject of the template. (In this
case, the definition is the function
swap)
template <typename T>
void swap(T &a, T &b) {
T c = a;
a = b;
b = c;
}
“Placeholder variables” have one value
within each template declaration. Think of
them as being replaced by whatever type
you specify the template to be.
Here we have a list of “placeholder
variables.” In almost all cases, they
will be specified with either the
typename or class keywords.
These two keywords are
equivalent.
Template Syntax:
Using it
Using a template
To use a template, one has to specialize
it. This is why it isn’t quite a generic
function. It does static polymorphism. It
morphs itself to the right type at
preprocess time (explained later!).
template <typename T>
void swap(T &a, T &b) {
T c = a;
a = b;
b = c;
}
Syntax
To explicitly specialize a template, write its name with the arguments for the
placeholder variables in angle brackets. This method always works.
Example:
double d1 = 4.5, d2 = 6.7;
swap<double>(d1, d2);
Templates however can auto-sense their placeholder values if all information
about what the placeholders represent can be inferred from the context
(arguments, and for member functions, the associated class instance). This is
called implicit specialization. In the previous case, the compiler is smart
enough to figure out that T is a double even without the explicit <double>
since the arguments are doubles. Thus this shorthand works:
Example:
swap(d1, d2);
How they Work:
Compilation 101
Preprocessor
Resolves #define, #include,
comments, templates
Compiler
Translates code to Machine
Language. It outputs an “object
file.” This code is not executable
Libraries
Source code
(text)
.c .h
Preprocessor
C/C++ code
(text)
Object code
(bin)
Linker
Takes object files and resolves
references to functions and
variables in different files
.c .h
Native Executable
(bin)
Compiler
Linker
executable
When you build an executable from a C++ source file, the preprocessor removes
all the things listed under “Preprocessor.” The result is pure C++ code (no
comments, templates, #includes, etc). This code is then compiled and linked.
All good programmers understand this process well.
How they Work:
Compiler, Linker
What does it mean to compile?
• The term “compile” is somewhat ambiguous. Often, when people say
“compile” they mean “build.” In the formal sense, it means turning one
language into another language. With C++, this generally means turning
C++ source code into Machine Code.
• Each C++ source file is usually compiled into an object file that contains the
code of all its defined functions. At this point, if you call a function from a
library or another file, the object code (stuff in the object file) only says “this
function exists somewhere and I want to use it”
• This if formally what the compiler is. When you get syntax errors, this is
usually the compiler talking to you (as opposed to the linker or preprocessor).
How they Work:
Compiler, Linker
What does it mean to link?
• After compilation happens, all the object files need to be linked together into
a final executable. All “I want this function” stubs in the object files have to
actually be resolved to some block of machine code and then the resulting
executable has to be formatted in a way the operating system can understand.
• If you write a prototype for a function, but forget to define it, your code will
compile but it won’t link. Link errors are usually harder to track, as the
linker can’t always give you line numbers (the linker only looks at the object
files and knows nothing about the original source).
How they Work:
Preprocessor
What is the preprocessor?
The preprocessor, formally, deals with the directives that start with a # sign (like
#include, #define). However, here, the term will be used to mean everything
that happens before the compiler gets the stuff to turn into machine code.
The relevant (in relation to templates) things the preprocessor does are:
• Replaces all #include statements and with the files they refer to.
• Removes all comments.
• Replaces all #defines macros with their value
• Generates actual code from templates
Templates do not exist!
• Templates are a preprocessor construct. They are cookie-cutters with which
the preprocessor generates real C++ code. When a template is used, (that is,
specialized, implicitly or explicitly), it get instantiated.
• The instantiation tells the preprocessor to create a version of the template
where each placeholder is replaced by its specialization. At this point, the
specific version of the template comes into existence and can be compiled. It
does not exist otherwise!
• In a very real way, a template just does a search and replace for each type you
specialize the template for. In essence, you are doing the same as writing a
bunch of overloaded functions. It’s just done for you, behind your back.
How they Work:
Consequences
Problem:
Templates are resolved in the preprocessing stage, so they don’t exist to
the compiler until they get instantiated. This is the balance between trying
to make templates work transparently, and trying to make things efficient.
Effects:
• Template code will not get compiled until used (and thus instantiated).
Thus, the compiler will not catch syntax errors until the template is used.
• This holds at the level of individual methods. They are not compiled
unless they are used.
Class Templates:
Class Definition
Syntax:
Templated classes basically follow the same syntax as templated
functions. However, the rules for which templated classes can infer their
specialization (see Template Syntax) are a bit more convoluted.
Before moving on, a bit of review on templated functions:
Are the following two templates equivalent?
template <typename T>
void swap(T &a, T &b) {
T c = a;
a = b;
b = c;
}
template <class C>
void swap(C &a, C &b) {
C c = a;
a = b;
b = c;
}
Answer:
Yes, they are equivalent. This may be relevant when writing class
templates as it is possible that a situation may arise where two definitions
are written for the same thing. If this happens, the program will not build
since there are two equivalent function definitions. The name of the
placeholder doesn’t matter, and “typename” and “class” can be used
interchangeably. Just something to remember.
Class Templates:
Example
Example: A templated, dynamic, 2 dimensional array (Matrix)*
Notice the only addition to
the class definition is the
line:
template <typename T>
Within the the
definition block,
the placeholder T
can be used as a
data type. When
the template is
specialized, it
takes on the value
of the
specialization.
File: Matrix.h
#ifndef MATRIX_H
#define MATRIX_H
template <typename T>
class Matrix {
public:
Matrix(int rows, int cols);
Matrix(const Matrix &other);
virtual ~Matrix();
Matrix& operator=(const Matrix &rhs);
T* operator[](int i);
int getRows() const;
int getCols() const;
protected:
void copy(const Matrix &other);
private:
Matrix();
int m_rows;
int m_cols;
T *m_linArray;
};
The header is pretty
pedestrian. Let’s have
some fun. 
On to the class
implementation.
#endif /* MATRIX_H */
*A commented version of this code is provided separately. It wouldn’t fit on the slide. 
Class Templates:
Example cont’d
#include "Matrix.h"
template <typename T>
Matrix<T>::Matrix()
{}
template <typename T>
Matrix<T>::Matrix(int rows, int cols) {
m_rows = rows;
m_cols = cols;
m_linArray = new T[m_rows * m_cols];
}
template <typename T>
T* Matrix<T>::operator[](int i) {
return m_linArray + (i*m_cols);
}
template <typename T>
void
Matrix<T>::copy(const Matrix &other) {
m_rows = other.m_rows;
m_cols = other.m_cols;
int size = m_rows * m_cols;
m_linArray = new T[size];
for( int i=0; i < size; i++ ) {
m_linArray[i] =
other.m_linArray[i];
template <typename T>
Matrix<T>::Matrix(const Matrix &other) {
copy(other);
}
template <typename T>
Matrix<T>::~Matrix() {
delete[] m_linArray;
}
template <typename T>
Matrix<T>&
Matrix<T>::operator=(const Matrix &other) {
if( this != &other ) {
delete[] m_linArray;
copy(other);
}
return *this;
}
}
}
template <typename T>
int Matrix<T>::getRows() const {
return m_rows;
}
template <typename T>
int Matrix<T>::getCols() const {
return m_cols;
}
File: Matrix.cc
The next slide explains
all this. It wouldn’t fit
on this slide. 
Class Templates:
Member Functions Dissected
Again, a templated class name by itself
has no meaning (eg. Matrix by itself
means nothing). It only gets meaning
through specialization, explicit or implicit.
Thus, when referring to an instance of a
templated class (a specific specialization),
the class name must be explicitly
specialized.
Here, the template has been
implicitly specialized by its context.
It is within the specialization region
of the class scope. Thus it does not
need the template arguments. For a
class definition, the specialization
region is the class block.
template <typename T>
Matrix<T>&
Matrix<T>::operator=(const Matrix &other) {
if( this != &other ) {
this->~Matrix();
copy(other);
}
return *this;
}
specialization region of
Notice that the
Matrix<T>::
specialization
region does not include the
return type. Thus the return
type needs explicit
specialization
This may be
obvious, but
remember that
though constructors
and destructors
have the same name
as a the class
template, they are
functions and do not
need to be
specialized.
Class Templates:
Dark Arts (usage)
Syntax
• Templated classes must be explicitly specialized. Thus, to create a 2
dimensional Matrix of doubles using the last example, the syntax would be:
Matrix<double> m(3,3);
• This specialization during declaration in reality creates a new type – namely
Matrix<double>. This should be thought of as its own type, separate from
any other specialization of Matrix (so it is different from Matrix<int>, or
Matrix<Foo>, etc.) At this point, the instance behaves as any other
instantiated type – at least for compilation.
Now that you have the basics…
So, young Jedi, you want to become a template master. You think you know all the
tricks. Little do you know, your programming life now hangs in the balance. For
what comes next is the real lesson. The coming information is what makes people
shiver in fear when the word template is mentioned. Listen to what is presented
next closely lest the subtleties stay shrouded in shadows. Now is time to learn of
convention, for once you break from convention, you enter the dark side of
template hell, and forever shall it dominate your path… Mu ha ha ha
Shotgun Safety:
(Identifying the danger)
Danger Awareness
Problem
Templates do not exist until you use them. They must be instantiated. Unless
this is done explicitly, instantiation occurs at the first usage for all known
template definitions. Thus, consider this example. Compile with
g++ -Wall –ansi main.cc Matrix.cc
Looks innocent, but it won’t link.
Quiz: What won’t link and why?
The link error happens with m1.getRows()
• Nothing from a template gets instantiated
until it is either used or explicitly
instantiated.
• Matrix<int>::getRows() const
does not get created until it is used at the
line with m1.getRows().
• The definition of the function is in Matrix.cc
and never used there.
• Thus the definition never gets created and
compiled to object code.
/* main.cc */
#include <iostream>
using namespace std;
#include “Matrix.h”
int main(void) {
Matrix<int> m1(3,4);
cout << m1.getRows() << endl;
}
Note: The compile line is actually wrong!
The file Matrix.cc only contains template
code. Since it is never used, it never
generates object code and shouldn’t be
compiled.
Shotgun Safety:
(know the routines)
3 conventions
There are three conventions to avoiding the link problem
• Write all the code inline in the .h files.
• Do the same as above, but kind of fake it by writing an implementation
file with your implementation and #include the implementation file in
your header file.
• Write the template as you would a normal class (using a header and an
implementation file). Then create a new source file and #include the
template implementation file there. This is the file which you compile,
not the template implementation. (See next slide for example)
The first two methods have the problem that anytime an implementation of a
function is changed, all code that uses it must be recompiled (not just
relinked). This is very slow on large builds. Also, the build process will
instantiate the template many more times than necessary which is a waste of
time and space. The third method is free from such problems. It also avoids
some other hurdles since it forces the instantiation of everything at one point.
Shotgun Safety:
An example
(faithfully practiced, prevents accidental loss of feet)
•
/* main.cc */
#include <iostream>
using namespace std;
•
#include <Matrix.h>
int main(void) {
Matrix<int> m1(3,4);
cout << m1.getRows() << endl;
}
Example:
To make the previously unlinking piece
of code link properly, it is necessary to
instantiate an integer version of the
Matrix template. The file would simply
look like this
compile line:
g++ –Wall –ansi main.cc MatrixInst.cc
•
•
The proper procedure
Write the template, separated into a
header and an implementation file
Create an instantiation file for the
template which include the
implementation file.
Compile the instantiation file and not
the template implementation file.
The instantiation file generates the
object code for the template.
/* MatrixInst.cc */
#include “Matrix.cc”
template Matrix<int>;
notice that the
implementation
(not header) file
is included.
This line forces the instantiation of the Matrix class
template, as well as all its member functions, for
specialization int. Other specializations require their
own lines.
Poison detection:
Pop Quiz
(be aware, so you know before it’s too late)
/*Foo.h */
#ifndef FOO_H
#define FOO_H
template <typename T>
class Foo {
Foo() {
b = “Hello Mom!”;
}
};
#endif /* FOO_H */
Will this compile?
• The unfortunate answer is yes, it will
compile. Even though b is
undeclared, it will “compile” because
nothing actually instantiates the
template, so the compiler never sees
the template code.
• This is why not explicitly forcing the
instantiation of class templates is
dangerous. You won’t know about an
error until you use it.
Because of this, some people believe that it is better to write a non-templated
version of a class first, and then make a template from that. Some beliefs,
however, are just wrong. That is one of them.
If templates are done properly with forced instantiation (see previous slide),
then this scenario will not occur.
Download