Συχνά είναι απαραίτητο να προδιαγράψουμε διαφορετική λειτουργικότητα σε ένα template με βάση τον τύπο των δεδομένων που θα εισαχθεί τελικά στην κλάση. Για παράδειγμα, για την κλάση Box<T> που είδαμε προηγουμένως, οφείλουμε να εισάγουμε διαφορετική λειτουργικότητα όταν ο τύπος δεδομένων είναι δείκτης αντί για κανονική μεταβλητή. Ο λόγος είναι ότι σε αυτή την περίπτωση δεν αρκεί η απλή αντιγραφεί του δείκτη, αλλά θα πρέπει να δεσμευθεί ο απαραίτητος χώρος στη μνήμη προκειμένου η κλάση να δημιουργήσει ένα αντίγραφο της υφιστάμενης πληροφορίας που περνιέται μέσω του δείκτη. Το παράδειγμα εξειδίκευσης της κλάσης Box<T> όταν περνιέται δείκτης δίνεται παρακάτω:
#ifndef _BOXTPTR_HPP_ #define _BOXTPTR_HPP_ #include "Box.hpp" template<typename T> class Box<T*> { T *e; public: Box(); Box(T* e); Box(const Box<T*>& b); ~Box(); T* get() const; void set(T* e); template <T*> friend std::ostream& operator<<(std::ostream& out, const Box<T*>& t); Box<T*>& operator=(Box<T*>& b); }; template <typename T> Box<T*>::Box() { this->e = nullptr; } template <typename T> Box<T*>::Box(T* e) { this->e = new T; *(this->e) = *e; } template <typename T> Box<T*>::Box(const Box<T*>& b) { this->e = new T; *(this->e) = *b.e; } template <typename T> Box<T*>::~Box() { delete this->e; } template <typename T> T* Box<T*>::get() const { T *ce = new T; *ce = *e; return ce; } template <typename T> void Box<T*>::set(T *e) { if(this->e != nullptr) delete this->e; this->e = new T; *(this->e) = *e; } template <typename T> std::ostream& operator<<(std::ostream& out, const Box<T*>& t) { T* ptr = t.get(); out << *ptr; delete ptr; return out; } template <typename T> Box<T*>& Box<T*>::operator=(Box<T*>& b){ set(b.e); return *this; } #endif
Παρακάτω παρατίθεται κώδικας ο οποίος χρησιμοποιεί την παραπάνω εξειδίκευση του template.
#include <iostream> #include "BoxPtr.hpp" #include "Student.hpp" using namespace std; int main() { int a=5; double d(4.23); Student kate = {"Kate", 1234}; Box<int*> intBox(&a); Box<double*> doubleBox(&d); Box<Student*> studentBox(&kate); Student* kate_ptr = studentBox.get(); cout << *kate_ptr << endl; delete kate_ptr; kate.setName("Katerina"); cout << kate << endl; }
Στον παραπάνω κώδικα αντικαταστήστε την εντολή #include “BoxPtr.hpp”
με την εντολή #include “Box.hpp”
και παρατηρήστε την αλλαγή στη συμπεριφορά του προγράμματος. To πρόγραμμα θα τερματίσει τη λειτουργία του (Γιατί?) με ένα μήνυμα της μορφής:
*** Error in `./BoxPtrUsage': double free or corruption (out): 0x00007ffc4f49ed10 *** ======= Backtrace: ========= /lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f40da1757e5] /lib/x86_64-linux-gnu/libc.so.6(+0x8037a)[0x7f40da17e37a] /lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7f40da18253c]
Το παραπάνω παράδειγμα είναι ικανοποιητικό εάν περαστεί ως δείκτης ένα αντικείμενο τύπου int* ή Student* , όμως δεν είναι ικανοποιητικό εάν περαστεί ένας δείκτης τύπου char* που αντιπροσωπεύει ένα C string. Σε αυτή την περίπτωση δεν αρκεί να δεσμευθεί η μνήμη για ένα χαρακτήρα, αλλά θα πρέπει α) να αναγνωρίσουμε το μέγεθος της συμβολοσειράς που πρέπει να αντιγραφεί και β)πριν γίνει η αντιγραφή της συμβολοσειράς να δεσμευθεί ο απαραίτητος χώρος για αυτή.
#ifndef _BOXCHARPTR_HPP_ #define _BOXCHARPTR_HPP_ #include "Box.hpp" #include <cstring> template <> class Box<char *> { char *e; public: Box(); Box(char *e); ~Box(); Box(const Box<char*>& b); char* get() const; void set(char *e); Box<char*>& operator=(Box<char*>& b); friend std::ostream& operator<<(std::ostream& out, const Box<char*>& t); }; Box<char*>::Box() { this->e = nullptr; } Box<char*>::Box(char *e) { this->e = new char[strlen(e)+1]; strcpy(this->e, e); } Box<char*>::Box(const Box<char*>& b) { this->e = new char[strlen(b.e)+1]; strcpy(this->e, b.e); } Box<char*>::~Box() { delete [] this->e; } char* Box<char*>::get() const { char *ce = new char[strlen(e)+1]; strcpy(ce, e); return ce; } void Box<char *>::set(char *e) { if(this->e!=nullptr) delete [] this->e; this->e = new char[strlen(e)+1]; strcpy(this->e, e); } Box<char*>& Box<char*>::operator=(Box<char*>& b){ set(b.e); return *this; } std::ostream& operator<<(std::ostream& out, const Box<char*>& t) { out << t.e; return out; } #endif
Παράδειγμα κώδικα που χρησιμοποιεί την παραπάνω εξειδικευμένη κλάση Box<char*> δίνεται παρακάτω:
#include <iostream> #include "BoxCharPtr.hpp" using namespace std; int main() { char greeting[64] = "Wellcome C++ in CE325 course"; Box<char*> greetingBox(greeting); char* greeting_copy = greetingBox.get(); cout << greeting_copy << endl; delete greeting_copy; Box<char*> msgBox = greetingBox; char* msg = msgBox.get(); cout << msg << endl; delete msg; }
Στον παραπάνω κώδικα αντικαταστήστε την εντολή #include “BoxCharPtr.hpp”
με την εντολή #include “BoxPtr.hpp”
και στη συνέχεια με την εντολή #include “Box.hpp”
. Παρατηρήστε την αλλαγή στη συμπεριφορά του προγράμματος. Σε τι οφείλεται αυτό;