1 / 38

Templates

Templates. Consider the following function, which swaps two integers: void swap(int &x, int &y) { int temp = x; x = y; y = temp; } int i = 3, j = 4; swap(i, j);. void swap(int &x, int &y) { int temp = x; x = y; y = temp; }

lerato
Télécharger la présentation

Templates

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Templates • Consider the following function, which swaps two integers: • void swap(int &x, int &y) { • int temp = x; • x = y; • y = temp; • } • int i = 3, j = 4; • swap(i, j);

  2. void swap(int &x, int &y) { • int temp = x; • x = y; • y = temp; • } • Now suppose we also want a function to swap floats. Then we write another function (we can overload the name “swap” for different argument types): • void swap(float &x, float &y) { • float temp = x; • x = y; • y = temp; • } • float f = 4.0, g = 5.0; • swap(f, g);

  3. But writing the same code over and over for every possible type can get tiring fast. What we’d really like would be a single swap function that worked for all types. Let’s express what we want as follows: • // For any type T, swap two elements of type T: • void swap(T &x, T &y) { • T temp = x; • x = y; • y = temp; • } • swap is supposed to take an two elements of some type (let’s call this type T for the moment), and swap them. But we don’t know what T is when we write swap - ideally, swap should work for all possible types T.

  4. C++ supports template declarations which express exactly what we want: • template <class T> void swap(T &x, T &y) { • T temp = x; • x = y; • y = temp; • } • This declares a template function swap which can swap any type. • A template is sort of like a function that takes a type as a parameter. The “class T” declaration indicates that T is an argument which can be instantiated with any type (despite the name “class”, this type can be any type, not just a class type).

  5. template <class T> void swap(T &x, T &y) { • T temp = x; • x = y; • y = temp; • } • swap can be instantiated with different types to produce swap functions specialized to particular types: • swap<int> is a function that swaps ints. • swap<string> is a function that swaps strings. • For example, swap<int> substitutes int for T to produce a function to swap ints: • void swap(int &x, int &y) { • int temp = x; • x = y; • y = temp; • }

  6. template <class T> void swap(T &x, T &y) { • T temp = x; • x = y; • y = temp; • } • Here’s an example that uses swap<int> to swap a couple of integers, and swap<float> to swap a couple of floating point numbers: • int i = 3, j = 4; • swap<int>(i, j); • float f = 4.0, g = 5.0; • swap<float>(f, g);

  7. template <class T> void swap(T &x, T &y) { • T temp = x; • x = y; • y = temp; • } • In practice, C++ can usually infer the right type parameter based on the arguments to a template function: • int i = 3, j = 4; • swap(i, j); // uses swap<int> • float f = 4.0, g = 5.0; • swap(f, g); // uses swap<float>

  8. To see how useful templates can be, recall the qsort function in stdlib.h: • void qsort(void *base, int num, size_t width, int (*compare)(void *elem1, void *elem2 ) ); • This was messy to use because of void*. For instance, our compare function for integers looked like: • int compareInt(void *i1Ptr, void *i2Ptr) { • if(*((int*)i1Ptr) < *((int*)i2Ptr)) return -1; • else if(*((int*)i1Ptr) == *((int*)i2Ptr)) return 0; • else return 1; • }

  9. We can use templates to get rid of the void*: • template <class T> void qsort(T *base, int num, int (*compare)(T& elem1, T& elem2 ) ); • This declares a template function qsort which can sort any type.

  10. template <class T> void qsort(T *base, int num, int (*compare)(T& elem1, T& elem2 ) ); • Example (notice that we can write a nice compareInt function without a bunch of casts): • int compareInt(int& i1, int& i2) { • if(i1 < i2) return -1; • else if(i1 == i2) return 0; • else return 1; • } • int iArr[4]; • iArr[0] = 3; • iArr[1] = 2; • iArr[2] = 7; • iArr[3] = 5; • qsort<int>(iArr, 4, compareInt);

  11. Notice also that the templates enforce a stronger typing property than the void* did: the type of the compare function passed in must match the type of the array. So • qsort(iArr, 4, compareInt); • typechecks, because compareInt and iArr both are built out of type int. But • string sArr[4]; • qsort(sArr, 4, compareInt); • won’t typecheck, because sArr doesn’t match the type of the compareInt function.

  12. Templates can also be used to create generic data structures. • Consider our old FloatArray class, which only worked for elements of type float: • class FloatArray { • float *arr; • int arrSize; • public: • FloatArray(int size); • FloatArray(const FloatArray& from); • FloatArray& operator = (const FloatArray& from); • ~FloatArray(); • float get(int i) const; • void set(int i, float f); • int size(); • };

  13. By declaring and implementing the class as a template, we can make it work for any type: • template <class T> class TArray { • T *arr; • int arrSize; • public: • TArray(int size); • TArray(const TArray<T>& from); • TArray<T>& operator = (const TArray<T>& from); • ~TArray(); • T get(int i) const; • void set(int i, T f); • int size(); • }; • For example, TArray<float> is a class for an array of floats, and TArray<string> is a class for an array of strings.

  14. template <class T> class TArray { • T *arr; • int arrSize; • public: • TArray(int size); • TArray(const TArray<T>& from); • TArray<T>& operator = (const TArray<T>& from); • ~TArray(); • T get(int i) const; • void set(int i, T f); • int size(); • }; • Example: • TArray<int> tArr(4); • tArr.set(3, 5); • int i = tArr.get(3);

  15. template <class T> class TArray { • T *arr; • int arrSize; • public: • TArray(int size); • TArray(const TArray<T>& from); • TArray<T>& operator = (const TArray<T>& from); • ~TArray(); • T get(int i) const; • void set(int i, T f); • int size(); • }; • Implementing member functions of a template class: • template <class T> int TArray<T>::size() { • return arrSize; • }

  16. Template functions and template classes can be used together. For instance, the following function sorts a TArray: • template <class T> void arraysort(TArray<T> &arr, • int (*compare)(T& elem1, T& elem2 ) ); • Notice again that the element type of the TArray must match the type used by the compare function. • Example: • TArray<int> tArr(4); • ... • arraysort<int>(tArr, compareInt);

  17. A template can take multiple type arguments: • template <class T, class U> class Pair { • public: • T x1; • U x2; • Pair(T a1, U a2): x1(a1), x2(a2) {} • }; • Pair<string, int> p1("deep ", 6); • cout << p1.x1 << p1.x2 << endl;

  18. The standard template library • There are a number of built-in data structures that are available for you to use, such as list and vector. • For instance, list<string> is a list of strings, and vector<Shape*> is a vector of pointers to Shapes. • Example: • #include <list> • #include <vector> • using namespace std; // necessary on some platforms • list<int> l1; // list of ints • vector<Shape*> v1; // vector of pointers to Shapes

  19. 2 dimensional arrays can now be implemented as vectors of vectors: • vector<vector<double> > arr; • Pitfall: the space in “> >” is necessary. If you type: • vector<vector<double>> arr; • then C++ thinks the >> means “right-shift”.

  20. Polymorphism strikes again • Remember how inheritance and virtual functions led to a form of polymorphism? A variable of type Shape could hold many different types of objects at run-time (Circles, Triangles, etc.). • Templates also provide a form of polymorphism. Inside a template<class T> function or class, a variable of type T may refer to many different types of objects, depending on what type T is instantiated with.

  21. Let’s see how these two forms of polymorphism compare. • Before templates were introduced into C++, many vendors provided data structure classes based on inheritance. Usually, all elements would have to be derived from a common base class called Object. • Then the data structure classes would work with Objects: • class Array { • Object **arr; • public: • ... • Object* get(int i) const; • void set(int i, Object* f); • };

  22. class Array { • Object **arr; • public: • ... • Object* get(int i) const; • void set(int i, Object* f); • }; • So if we write a class that inherits from Object, we can put objects of this class into an Array: • class MyString: public Object { • ... • }; • Array arr(5); • arr.set(0, new MyString(“hello”)); • arr.set(1, new MyString(“there”));

  23. class Array { • Object **arr; • public: • ... • Object* get(int i) const; • void set(int i, Object* f); • }; • But if we try to read a MyString object from the array, we find it has type Object* instead of MyString*: • Object *o = arr.get(0); • So we have to do a cast (yuck!) to make it a MyString*: • MyString *m = (MyString *) o;

  24. With templates, we know that the type we get out of a data structure is exactly the type we put into the data structure. So no cast is necessary: • template <class T> class TArray { • T **arr; • public: • ... • T* get(int i) const; • void set(int i, T* f); • }; • Array<MyString> arr(5); • arr.set(0, new MyString(“hello”)); • arr.set(1, new MyString(“there”)); • MyString *m = arr.get(0); // no cast necessary • So templates are clearly a nicer, safer way to implement generic data structure classes.

  25. On the other hand, inheritance is the best way to express a common interface shared by a collection of closely related classes. For instance, a collection of shapes can rely on a common base class Shape: • class Shape { • ... • public: • virtual void draw(); • virtual void rotate(double angle); • virtual double area(); • }; • class Circle: public Shape {...}; • class Triangle: public Shape {...};

  26. Suppose we didn’t use inheritance to implement shapes, and instead defined each shape independently, without a common base class: • class XCircle { • ... • void draw(); • void rotate(double angle); • double area(); • }; • class XTriangle { • ... • void draw(); • void rotate(double angle); • double area(); • };

  27. Now suppose we wrote a template function rotateAndDraw, designed to work for XCircles and XTriangles: • template<class T> • void rotateAndDraw(T &s, double angle) { • s.rotate(angle); • s.draw(); • } • The function assumes that T contains rotate and draw member functions, but it doesn’t document this clearly. For instance, it doesn’t say what argument and return types the rotate and draw functions should have. • This is bad style.

  28. It isn’t clear from the template declaration • template<class T> void rotateAndDraw(T &s, double angle); • what types are valid for T. For instance, is an int ok? • rotateAndDraw<int>(5, 3.0); // ??? • This in fact does not typecheck, because ints don’t have rotate and draw functions. However, we have to look at rotateAndDraw’s implementation to figure this out. • Using inheritance is much better in this case: • void rotateAndDraw(Shape &s, double angle) { • s.rotate(angle); • s.draw(); • } • Here, the type of s is clearly stated.

  29. Inheritance also allows more heterogeneity. Suppose we want a single list that holds all different kinds of shapes. • If shapes aren’t related by inheritance, we can create lists of specific types: • list<XCircle*> l1; • list<XTriangle*> l2; • But l1 can only hold XCircles, and l2 can only hold XTriangles. Neither list can hold a mixture of the two. If Circle and Triangle share the base class Shape, though, then • list<Shape*> l3; • can hold all different kinds of shapes, including Circles and Triangles.

  30. Notice that • list<Shape*> l3; • takes advantage both of templates and inheritance. • This combination of templates and inheritance is quite common and very useful. It uses the strengths of templates and inheritance in a complementary way.

  31. Templates: the good, the bad, the ugly • good: • templates are excellent at expressing generic algorithms and data structures • bad and ugly: • template implementations go into header files instead of .cpp files • template instantiations create multiple copies of a template’s code • template uses can’t be typechecked without expanding the entire template

  32. Problem #1: On most platforms, the entire implementation of a template must go in the header file: • // swap.h: • template <class T> void swap(T &x, T &y) { • T temp = x; • x = y; • y = temp; • } • // .cpp file: • #include “swap.h” • ... • This violates everything we’ve told you about source code organization. Usually only declarations go in the header file, and implementations go in a .cpp file.

  33. Problem #2: A different copy of a template’s code is produced for every different type passed in as the templates type parameter. • template <class T> void qsort(T *base, int num, int (*compare)(T& elem1, T& elem2 ) ); • For instance, if the qsort template above is instantiated for ints, floats, and strings, then 3 different copies of qsort’s code are generated: • qsort<int>(...); // generates code for qsort<int> • qsort<float>(...); // generates code for qsort<float> • qsort<string>(...); // generates code for qsort<string>

  34. If the templates are complicated, then this “code bloat” can get quickly out of hand. C++ programmers sometimes complain that their executable files are many megabytes in size because of template instantiation. • By contrast, the old qsort in stdlib.h only required one copy of the code, which worked for all types (through the admittedly clunky void* mechanism).

  35. Problem #3: typechecking template instantiations • template<class T> • void rotateAndDraw(T &s, double angle); • We’ve already seen the rotateAndDraw example that expects its type T to have rotate and draw member functions. This means that trying to use • rotateAndDraw<int>(5, 3.0); • won’t typecheck. But how do we know that it won’t typecheck? We have to look through rotateAndDraw’s implementation, to see what things it does with the objects of type T. • This violates the idea of separating the implementation of a function from its interface.

  36. A practical consequence of this is that C++ needs to expand out uses of a template entirely before it can typecheck them. • If the template is complicated, This leads to remarkably obscure type errors when something goes wrong. Here’s a small (incorrect) example that uses the standard library class map: • map<string, int> m1; // maps strings to ints • // incorrectly tries to insert a pair (“hello”, 6) • // into the map: • m1.insert(m1.begin(), make_pair("hello", 6)); • This incorrectly passes in a char* argument (“hello”) to make_pair instead of a string. Let’s see what type error we get.

  37. map<string, int> m1; // maps strings to ints • m1.insert(m1.begin(), make_pair("hello", 6)); • Visual Studio 5.0 reports the following type error: • error C2664: 'class std::_Tree<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char>>,struct std::pair<class std::basic_string<char,struct std::char_traits<char>, class std::allocator<char>>,int>,struct std::map<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char>>,int,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char>>>,class std::allocator<int>>::_Kfn,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char>>>,class std::allocator<int>>::iterator std::map<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char>>,int,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char>>>,class std::allocator<int>>::insert(class std::_Tree<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char>>,struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char>>,int>,struct std::map<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char>>,int,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char>>>,class std::allocator<int>>::_Kfn,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char>>>,class std::allocator<int>>::iterator,const struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char>>,int> &)' : cannot convert parameter 2 from 'struct std::pair<char [6],int>' to 'const struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char>>,int> &'

  38. Don’t be scared off too much by the problems with C++ templates. Templates are still a good idea! But we need to remember that templates are best used for small, simple container classes. Large and complicated templates (such as the map example above) often lead to code bloat and difficult error messages. • Simple classes, like list and vector, tend to work very well in practice. We’ll explore these standard library classes in the next lecture.

More Related