Προγραμματισμός με C++ Templates

dimitris | Κυρ, 01/06/2013 - 09:36 | 27' | 2

Μια απλή εισαγωγή στο πιο προηγμένα στοιχεία της C++, τα templates...

Του Γιώργου Μακρυδάκη*
Unknown Object

Ένα από τα πιο απλά προβλήματα για κάθε πρόγραμμα είναι η χρήση και η διαχείριση συμβολοσειρών (strings). Στην περίπτωση της C++, ένας τρόπος αναπαράστασης του συγκεκριμένου τύπου δεδομένων για χαρακτήρες τύπου char είναι ο std::string, ενώ για χαρακτήρες τύπου wchar_t (wide) o std::wstring. Ακολουθεί μία εξαιρετικά απλοϊκή έκδοση της βασικής διδακτικής προγραμματιστικής ενότητας κάθε γλώσσας: το Hello World!

#include <string>
int main()
{
 std::string hello("Hello, World");
 return(0);
}

O παραπάνω κώδικας είναι απόλυτα ισοδύναμος με τον παρακάτω:

#include <string>
int main()
{ // whitespace στα > > ...
 std::basic_string<char, std::char_traits<char> > hello("Hello, World");
 return(0);
}

Θα μπορούσαμε να συνεχίσουμε ακόμη περισσότερο στον αναλυτικότερο τρόπο γραφής του std::string, όπως τον βλέπει τελικά ο μεταγλωττιστής της C++, αλλά έχει γίνει προφανές ότι η χρήση του std::string αποτελεί απλά μία συντακτική απλούστευση μίας πολύπλοκης κλάσης basic_string στο namespace std, ενός typedef όπως λέγεται μίας βασικής κλάσης που παραμετροποιείται ώς προς τον τύπο του χαρακτήρα (char, wchar_t) και άλλων χαρακτηριστικών που αποτελούν απόρροια των ιδιοτήτων του εκάστοτε τύπου αναπαράστασης χαρακτήρων (char, wchar_t).

Από αυτό το σημείο ξεκινά η εισαγωγή σε ένα από τα πιο ενδιαφέροντα χαρακτηριστικά της C++, ήτοι τα C++ templates. Ένα ιδιαίτερο χαρακτηριστικό που πραγματικά ξεπερνά και τον ίδιο του τον ορισμό όσον αφορά τις συνέπειες της χρήσης του.

Σχέση με Preprocessor, Compiler & Linker

Ξεκινώντας από τον πηγαίο κώδικά μας (sources), η διαδικασία που παράγει την τελική μας εφαρμογή περνά πρώτα από τον Preprocessor (προεπεξεργαστή) ο οποίος επεξεργάζεται οποιαδήποτε αναφορά όπως σχόλια και το σύνολο των σχετικών εντολών που αποτελούν τα βασικά βήματα ώστε να μετατραπεί ο αρχικός πηγαίος κώδικας σε καθαρή σειρά εντολών C++. Σημειώνεται οτι οι αλλαγές γίνονται μέχρις στιγμής σε επίπεδο κειμένου και δεν έχει παραχθεί αρχείο σε γλώσσα μηχανής. Μόλις περατωθεί επιτυχώς αυτή η διαδικασία, το αποτέλεσμα μεταβιβάζεται σε δεύτερο επίπεδο στον Compiler (μεταγλωττιστή), ο οποίος αναλαμβάνει την περαίωση του δεύτερου βασικού βήματος: τη μετατροπή του κειμένου σε γλώσσα μηχανής.

Όλα αυτά έχουν ως αποτέλεσμα τη δημιουργία των λεγόμενων object files τα οποία δεν αποτελούν άμεσα εκτελέσιμο κώδικα, αφού περιέχουν αποκλειστικά και μόνο την αντίστοιχη "μεταγλώττιση" του πηγαίου κώδικα των αρχείων που παραδόθηκαν από τον προπεπεξεργαστή σε γλώσσα μηχανής σε μονοσήμαντη αντιστοίχιση, δηλαδή συνάρτηση προς συνάρτηση, κλάση προς κλάση, μεταβλητή προς μεταβλητή, κλπ. Με το πέρας της διαδικασίας, τα object files παραδίδονται στον Linker ο οποίος και αναλαμβάνει να τα συνδυάσειτόσο μεταξύ τους, όσο και με αρχεία εκτός του δικού μας πηγαίου κώδικα, αλλά που είναι απαραίτητα για την εκτέλεση του τελικού προγράμματος. Αυτά τα απαραίτητα αρχεία είναι οι Libraries (βιβλιοθήκες). Με το linking, φτάνουμε στο τελικό, άμεσα εκτελέσιμο πρόγραμμα.

Τα C++ Templates αποτελούν χαρακτηριστικό της C++ το οποίο ανήκει στο σύνολο των εντολών του Μεταγλωττιστή (Compiler). Μεταξύ των εργασιών σε επίπεδο μεταγλωττιστή, οι σημαντικές σε σχέση με τα C++ templates διαδικασίες είναι η αντικατάσταση τόσο των implicit όσο και των explicit "εξειδικεύσεων" (instantiations) τους, με αποτέλεσμα να προκύπτει κώδικας C++ ο οποίος έχει παραμετροποιηθεί ώς προς τον συγκεκριμένο τύπο (ή και τύπους, με οποιοδήποτε μεταξύ τους συνδυασμό) που έχουμε επιλέξει. Από αυτό το σημείο και πέρα, τα templates "εξαφανίζονται" από τον κώδικά μας καθότι έχουν εκτελέσει το σκοπό τους.

Μία άλλη ιδιαιτερότητα, με άμεσες συνέπειες για τον τρόπο που μπορούμε να χρησιμοποιήσουμε τα C++ templates, είναι ότι αποτελούν ένα Turing Complete σύστημα. Με απλά λόγια, είναι δυνατό να γραφεί μιας οποιαδήποτε πολυπλοκότητας πρόγραμμα εξολοκλήρου σε templated κώδικα. Το πλεονέκτημα; Η επεξεργασία του προγράμματος αυτού γίνεται κατά την διάρκεια του χρόνου μεταγλώττισης (το λεγόμενο "compile time"), με το πέρας των εργασιών του προεπεξεργαστή. Το γεγονός ότι προγραμματιστικά μπορούν να εκτελεστούν μία σειρά από πολύπλοκες διαδικασίες σε επίπεδο compile time ελαφρύνει το επεξεργαστικό βάρος σε επίπεδο χρόνου εκτέλεσης του προγράμματος (runtime). Μπορούμε να πούμε ότι με την βοήθεια των templates γράφουμε προγράμματα που γράφουν άλλα προγράμματα, μία τεχνική που ονομάζεται γενικά metaprogramming, και στην περίπτωση της C++, template metaprogramming.

Υπάρχει μία ειδοποιός διαφορά μεταξύ των templates και των προεπεξεργαστικών μακροεντολών (preprocessor macrocommands, γνωστές ώς macros). Οι macros είναι και αυτές ένα χαρακτηριστικό που αποτελεί στοιχείο χρήσιμο σε επίπεδο compile time, παράγοντας κώδικα μετά από επεξεργασία του αρχικού κειμένου του πηγαίου κώδικα σε επίπεδο απλής αντικατάστασης. Οι macros όμως, έχουν περιορισμένη ισχύ ως εργαλείο καθορισμού της ροής ενός προγράμματος σε επίπεδο compile time και κυρίως δεν έχουν τις ικανότητες της γλώσσας την οποία συνοδεύουν ως προς την επεξεργασία βασικών τύπων ή γραμματικού και συντακτικού ελέγχου των αντικαταστάσεων που επιτελούν. Εξαίρεση σε αυτό αποτελούν οι macros της Lisp.

Αρκεί να πούμε ότι η σωστή χρήση των templates αποτελεί ένα αρκετά πολύπλοκο "εργαλείο" που αποσκοπεί στην περισσότερο αποδοτική αξιοποίηση του compile time. Παράδειγμα αποτελούν βιβλιοθήκες C++ όπως η Blitz++ για επιστημονικούς υπολογισμούς, αρκετά μεγάλο τμήμα της Boost (σειρά απο πολύ χρήσιμα σύνολα εντολών) και αναρίθμητες άλλες. Αρκεί να πούμε οτι και η γνωστή σε όλους, συνοδεύουσα βασική βιβλιοθήκη της C++, η STL (Standard Template Library), όπως δηλώνει το ακρωνύμιο της, είναι με βιβλιοθήκη χτισμένη πάνω σε αυτό το χαρακτηριστικό της C++.

Βασικά είδη των C++ Templates

Μπορούμε να διαχωρίσουμε τα templates σε δύο βασικά είδη: Class Templates (templates τύπου κλάσης ή class) και τα Function Templates (συναρτησιακά templates). Στο συγκεκριμένο άρθρο θα επικεντρωθούμε στην κατανόηση των Class Templates λόγω του ότι αποτελούν συνολικά τον καλύτερο δυνατό τρόπο για την επεξήγηση αυτού του χαρακτηριστικού της γλώσσας.

Σημειολογία Κλάσεων C++

Στη C++ μία κλάση (class) αποτελείται από ένα σύνολο μελών που απαρτίζονται από τύπους δεδομένων ή/και μεθόδους χρήσης ή/και μεταβολής αυτών των δεδομένων με τρόπους που ορίζονται από τον προγραμματιστή. Η πρόσβαση στα διάφορα μέλη, είναι τριών ειδών: private (πρόσβαση αποκλειστικά και μόνο από τα μέλη του συνόλου που απαρτίζουν την κλάση), protected (πρόσβαση απο μέλη άλλων κλάσεων οι οποίες έχουν μία σχέση κληρονομικότητας ώς προς την κλάση αυτή) και public (πρόσβαση απο οποιοδήποτε εσωτερική ή εξωτερική προγραμματιστική επιρροή). Στην περίπτωση των C++ structs (δομών) υπενθυμίζουμε ότι πρόκειται για κλάσεις με εξ'ορισμού public πρόσβαση για όλα τα μέλη, ενω οι C++ classes έχουν το αντίθετο, επίσης για όλα τους τα μέλη (private) . Οι κλάσεις αποτελούν βασικό στοιχείο του αντικειμενοστραφή προγραμματισμού, ο οποίος μπορεί να περιγραφεί ως ένας τρόπος σύνδεσης των δεδομένων μας με τις συναρτήσεις που τα επεξεργάζονται, σε κλάσεις (αντικείμενα) ώστε να επικεντρώνεται η προσοχή μας καθαρά και μόνο στις μεταβολές και την επεξεργασία που επιθυμούμε για αυτά τα αντικείμενα.

Στη περίπτωση της κλάσεων της C++, έχουμε μεταξύ των διαφόρων μελών, δύο συγκεκριμένες κατηγορίες, τους constructors (κατασκευαστές) και τους destructors (καταστροφείς), οι οποίοι δημιουργούν και καταστρέφουν, αντίστοιχα, τις κλάσεις.

Οι constructors καλούνται πάντοτε κατά την αρχικοποίηση της κλάσης - αντικείμενο, και ασχολούνται κυρίως με την αρχικοποίηση των δεδομένων μας, την δέσμευση των σχετικών τμημάτων μνήμης του υπολογιστή. Το ιδίωμα της γνωστής για τους προγραμματιστές C++ λίστας αρχικοποίησης (initialization list), βελτιστοποιεί την απόδοση των διαδικασιών κατά την δημιουργία των αντικειμένων μας και αυτό είναι που θα χρησιμοποιήσουμε στα παραδείγματά μας. Σε μια λίστα αρχικοποίησης, ουσιαστικά ελέγχουμε την αρχικοποίηση των δεδομένων μας δια της σειριακής κλήσης των constructors των μελών που απαρτίζουν την κλάση μας.

Οι destructors, εκτελούν την αντίθετη διαδικασία, αποδεσμεύοντας τους όποιους πόρους έχουμε δεσμεύσει στο σύστημά μας. Οι constructors και οι destructors έχουν το ίδιο όνομα με τις κλάσεις στις οποίες αναφέρονται, με την διαφορά ότι οι destructors έχουν ως πρόθεμα το σύμβολο ~ (tilde) κατά τη γραφή τους. Ο ορισμός ενός destructor από τον προγραμματιστή δεν είναι πάντοτε απαραίτητος, και εξαρτάται απο τις διαδικασίες που ακολουθούνται στους constructors.

Οι constructors και destructors δεν έχουν ποτέ τιμή επιστροφής (return value), που είναι και ένας λόγος για τον οποίο δεν έχουν προθεματική τυποποίηση (δεν έχουν ούτε καν το πρόθεμα void).

Η αναδρομή στα παραπάνω χαρακτηριστικά ήταν απαραίτητη ως βάση για την παραπέρα κατανόηση των Class Templates στη C++. Ο λόγος είναι εξαιρετικά απλός: Τα templates απλά παραμετροποιούν τον τύπο των δεδομένων που δέχονται οι κλάσεις μας, και δε μεταβάλλουν τους αλγόριθμους που εφαρμόζουμε για την επεξεργασία αυτών ή την οποιαδήποτε άλλη υποδομή. Τα templates εφαρμόζουν ουσιαστικά τις αρχές του generic programming (γενικού προγραμματισμού) στη C++, αποτελώντας ένα όχημα περιγραφής αλγορίθμων που ανεξαρτητοποιείται από τον υποκείμενο τύπο μέχρι και την οριστική παραμετροποίηση των κλάσεών μας σε επίπεδο preprocessor (βλ. αρχικό παράδειγμα με την std::basic_string<char>).

ΤΑ KEYWORDS ΤΗΣ C++

Παρακάτω αναφέρουμε τις σημαντικότερες λέξεις-κλειδιά (keywords) που συναντάμε σε κώδικα C++ καθώς και τι δηλώνει το καθένα...

typename
ενδεικτικό ονόματος τύπου ως προς τον οποίο παραμετροποιούμε την κλάση μας.

typedef
ενδεικτικό δημιουργίας εναλλακτικής ονομασίας ώς για την κλήση τύπου (ουσιαστικά ένα alias).

namespace
Συλλέκτης διάφορων προγραμματιστικών κατασκευασμάτων.

class
Eνδεικτικό ορισμού κλάσης της C++.
 

iterator
Επιτρέπει τη πρόσβαση δεδομένων για κλάσεις τύπου container (συλλέκτη) όπως std::vector, std::list, std::map, κοκ. Κάθε κλάση-container έχει διαφορετικά χαρακτηριστικά, και μπορούμε να παραμετροποιήσουμε τις κλάσεις της C++ που έχουμε ακόμη και ως προς τον τύπους αυτούς (σε αυτή την περίπτωση η παραμετροποίηση γίνεται με την χρήση "template template" (sic) παραμέτρων).

scope operator (::) 
Φέρνουμε στο προσκήνιο (scope) το προγραμματιστικό στοιχείο που ανήκει σε συγκεκριμένο namespace, κλάση, struct κλπ του οποίου το ενδεικτικό κτήσης μπαίνει ως πρόθεμα (prefix) στον scope operator, ενώ ως επίθεμα (suffix) βάζουμε το στοιχείο που μας ενδιαφέρει. Π.χ. std::vector, std::string, κλπ. Τα όρια συγκεκριμένου scope βρίσκονται πάντοτε εντός των συμβόλων { και }.

dot operator (.)
Δίνει πρόσβαση σε μέθοδο συγκεκριμένης κλάσης, σύμφωνα με τους κανόνες πρόσβασης (private, protected, public). Π.χ. το str.size() καλεί τη μέθοδο size() του string str.

function object
Επιτρέπει σε ένα αντικείμενο να συμπεριφέρεται τόσο ως συνάρτηση όσο και ως αντικείμενο, συνήθως με το ίδιο συντακτικό. Στη C++, ένας functor μπορεί να χρησιμοποιείται αντί μίας συνάρτησης () ορίζοντας μία κλάση που "υπερφορτώνει" (overloading) το μέλος operator(), και σε αυτή την περίπτωση έχουμε έναν class type functor. Διάφοροι άλλοι τύποι function objects μπορούν να οριστούν στη C++, ενώ ο συνδυασμός τους με τα templates επιτρέπει στην C++ την χρήση τεχνικών συναρτησιακού προγραμματισμού (functional programming). Αρκετά τμήματα της STL χρησιμοποιούν function objects στην υλοποίησή τους. Κλασσικό παράδειγμα χρήσης ενός functor είναι στο Function Template std::for_each της STL.

template typedef
Για το τρέχον C++ Standard δεν μπορούμε να ορίσουμε ένα "template typedef". Για ακόμη μια φορά, όμως, ο σχεδιασμός της γλώσσας μας επιτρέπει την υλοποίησή του γιατί όχι μόνο μπορούμε να έχουμε class templates, αλλά μπορούμε και να έχουμε και ορισμούς typedef σε αυτές τις "templated" κλάσεις. Για το συντακτικό παράδειγμα και τη χρήση, έχουμε ως σημείο αναφορας το CourseList "template typedef" για το container class std::map και τον συνοδό του iterator στο study.h [στο DVD]. Το template typedef γίνεται πιο εύκολο στη σύνταξή του στο C++0x Standard.

Το παράδειγμα

Δεδομένου ενός συνόλου (κλάση Group) μαθητών (κλάση Student) που έχουν τα ίδια μαθήματα στα οποία ο καθένας έχει συγκεκριμένη βαθμολογία, θέλουμε να δούμε αναλυτικά τόσο την βαθμολογία (score) σε κάθε ένα από αυτά, όσο και τον μέσο όρο. Το στοιχείο ως προς το οποίο θα χρησιμοποιήσουμε ως παράμετρο για τις templated κλάσεις μας είναι ακριβώς ο τύπος της μεταβλητής στην οποία θα αποθηκεύσουμε το score: unsigned int, int, double, κλπ. Κατά τη γνωστή πρακτική C/C++, έχουμε τοποθετήσει το κώδικά μας σε ένα αρχείο "header" με όνομα study.h στο οποίο έχουμε αναλυτικά των κώδικα των κλάσεων που χρησιμοποιούμε για την επίλυση του προβλήματος.

Σε αυτό το σημείο είναι απαραίτητο να πούμε ότι όλα τα είδη κώδικα που βασίζεται σε templates, στη τρέχουσα υλοποίηση του C++03 Standard, πρέπει να έχουν τόσο το declaration όσο και το definition και το implementation των διαφόρων μελών των κλάσεων / συναρτήσεων κλπ στο ίδιο αρχείο, για εκείνα τα τμήματα του κώδικα που αναφέρονται σε templates. Σε κάθε κανόνα υπάρχουν οι εξαιρέσεις βέβαια, και ο σχεδιασμός της C++ επιτρέπει τον διαχωρισμό των διαφόρων τμημάτων, μέσω μιας τεχνικής γνωστής και ως explicit template instantiation. Η χρήση αυτής της τεχνικής έχει αρκετές πρακτικές και θεωρητικές συνέπειες, η αμεσότερη των οποίων είναι η μείωση του απαιτούμενου χρόνου για την παραγωγή των εκτελέσιμων αρχείων αλλά ξεπερνάει τους σκοπούς του παρόντος άρθρου.

Υλοποίηση

Στο παράδειγμα του κώδικα που βλέπετε στην παρακάτω εικόνα, έχουμε ορίσει τις προαναφερθείσες κλάσεις Student και Group. Εκτός από αυτές και τις λεπτομέρειες σχετικά με τα διάφορα μέλη τους, παρατηρούμε την ύπαρξη δύο συγκεκριμένων "κλάσεων" οι οποίες έχουν μία ειδοποιό διαφορά ως προς τις δύο κλάσεις (Student, Group) που θέλουμε για την επίλυση του προβλήματος. Δεν έχουν δηλωμένους constructors, και όλα τους τα μέλη είναι άμεσα προσβάσιμα στην έξω από αυτές προγραμματιστική ροή.

Αναφερόμαστε στην CourseList (που εφαρμόζει αυτό που λέμε συνήθως template typedef) και στην ιδιόμορφη κλάση display_students (η συγκεκριμένη αποτελεί ένα function object, μια συνάρτηση-αντικείμενο).

"
Εικόνα - κάντε κλικ για μεγέθυνση!

Ο κώδικας του study.h μας επιτρέπει να καταλάβουμε την υφή των κλάσεων Study και Group αρκετά εύκολα. Ας αφήσουμε προσωρινά την σχετική σύνταξη των templates, και ας επικεντρωθούμε στα διάφορα μέλη. Έχουμε λοιπόν δύο (template) κλάσεις, η κάθε μία εκ των οποίων έχει μέλη με προσβασιμότητα τύπου private (μόνο δεδομένα σε αυτή τη περίπτωση), και μεθόδους με προσβασιμότητα public, όπως π.χ. τη μέθοδο addCourseVote() της Student, που προσθέτει βαθμό ανά μάθημα, την addStudent() της κλάσης Group, που προσθέτει ένα αντικείμενο τύπου Student στη Group, και τη showStudents(), που δείχνει συνολικά κάποια στατιστικά στοιχεία για τη συλλογή αντικειμένων τύπου Student εντός της Group).

Επιστρέφουμε λοιπόν στα templates. Και επικεντρώνουμε τη προσοχή μας στα δύο keywords της C++ που ορίζουν τα διάφορα τμήματα ως templated κώδικα, δηλαδή τα template και typename. Παρατηρήστε τη σύνταξη στις πρώτες γραμμές των κλάσεων CourseList και Student:

template<typename T> class ... {...};

Το typename ενημερώνει τον Compiler για το σημείο στο οποίο πρέπει να γίνει η αντικατάσταση και η τύπο - εξάρτηση (template instantiation), η οποία έχει διαφορετικό νόημα από μία απλή macro. Με απλά λόγια, το πρόθεμα template<typename T> δηλώνει ότι έχουμε ένα template, με μια τύπο-παράμετρο (typename parameter) T. Στη περίπτωση περισσοτέρων παραμέτρων, διαδοχικά typename εντός του template< ... > χωρίζονται από τον τελεστή κόμμα, όπως π.χ. το template<typename T, typename R = int>. Εδώ, το default για το R είναι ο τύπος int. Πάντοτε οι default τύποι δηλώνονται κατά σειρά τελευταίοι, ή ακόμη template<typename T, int>. Προσοχή στο τελευταίο: εδώ το int δεν είναι typename παράμετρος, alla non - type. δηλαδή, δεν αντικαθίσταται από άλλο τύπο...

Αναλύοντας τη κλάση Group, παρατηρούμε ένα "περίεργο" συντακτικό:

template<typename T, template<typename A, typename = std::allocator<Α> > class I = std::list>
class Group {...};

Αντί για το προσφιλές typename, βρίσκουμε ένα άλλο template ( και ένα άλλο ακόμη, το std::allocator, ασχολείται με την δέσμευση μνήμης), με επίσης δικές του παραμέτρους στο εσωτερικό, ενώ βλέπουμε και το class να συμμετέχει στην όλη διαδικασία. H δεύτερη παράμετρος είναι ένα template template parameter (TTP). Σε αυτή τη περίπτωση, θέλουμε να παραμετροποιήσουμε _και_ τον τύπο της container κλάσης για τα αντικείμενα τύπου Student. Γνωρίζουμε ότι για την έκδοση της STL που χρησιμοποιούμε, κάποια templated container classes όπως το vector, list ορίζονται ώς

template<typename T, ... > class ...

Ενώ αυτές οι κλάσεις έχουν επίσης και κοινό μέλος - μέθοδο για την προσθήκη των classes για τα οποία είναι class containers (όπως τη μέθοδο push_back, βλ παράδειγμα κώδικα). Όλα αυτά μας βοηθούν στο να μπορέσουμε να έχουμε και διαφορετικό τύπο container class για τα Student αντικείμενα

Για να μπορέσουμε να χρησιμοποιήσουμε αυτά τα templates σαν παραμέτρους σε ένα δικό μας template, πρέπει να γνωρίζουμε τις απαιτούμενες περιγραφικές παραμέτρους τους για τον C++ compiler. Σημείωνουμε ότι με το typename A = .. ή και typename = ... δίνουμε ένα λεγόμενο εξ'ορισμού τύπο στη παράμετρο που μας ενδιαφέρει (τον Α) ή απλά περιγράφουμε το γεγονός ότι υπάρχει ένας συγκεκριμένος τύπος που πρέπει να "δοθεί" στην template template παράμετρο για τη συντακτική ορθότητα. Γενικά η χρήση των template template παραμέτρων μπορεί να γίνει εξαιρετικά πολύπλοκη όταν απαλείφονται τα μη χρησιμοποιούμενα από τον compiler της γλώσσας typename parameters. Για λόγους πληρότητας, χρησιμοποιούμε τα TTP με διάφορους τύπους σύνταξης στο παράδειγμα μας. Η χρήση του class I για τον καθορισμό του template template parameter στην κλάση Group δεν μπορεί να αντικατασταθεί με ένα typename I.

Eπίλογος

Υπάρχουν άπειρα ακόμη παραδείγματα για το πώς χρησιμοποιούνται τα C++ templates. Η καλύτερη προσέγγιση στο πρόβλημα της κατανόησής τους είναι η χρήση τους, για τον ενδιαφερόμενο σε προγραμματισμό σε C++. Ενδεικτικά δείτε το γνωστό "The C++ Programming Language", του B. Stroustrup...

Κατεβάστε τον κώδικα του study.h και του templates.cc (μετονομάστε τα αρχεία txt στα σωστά ονόματα)

Δώσε αστέρια!

MO: 5 (ψήφοι: 1)

Σχόλια

Ενδιαφέρον το άρθρο, αν και ίσως θα μπορούσε να μην μπαίνει σε τόσο πολύπλοκα ζητήματα όπως τα TTP τόσο γρήγορα.

Πάντως έχω μία τεχνική παρατήρηση: αναφέρεται ότι η επεξεργασία των templates γίνεται από τον προεπεξεργαστή της C++, όμως αυτό δεν είναι ορθό. Τα templates τα επεξεργάζεται ο μεταγλωττιστής (compiler), ενώ ο προεπεξεργαστής ασχολείται με τις γραμμές που ξεκινούν από #.

Γειά σε όλους,

Σωστά, υπήρχε ένα σημαντικό λάθος στο τελικό κείμενο στην σχετική παράγραφο και στο σχήμα όπως έφυγε για το τυπογραφείο τότε (πρόκειται για αναδημοσίευση από το 2008 και για την C++03 από το τότε greek linux format) το οποίο και φυσικά διορθώθηκε στα errata των επομένων δύο της σειράς που πάντα αφορούν templates. Το υπόλοιπο του κειμένου το καθιστά σαφές ότι πρόκειται για λάθος αλλά με έμμεσο τρόπο. Ο Δημήτρης απλά δεν έβαλε και τις τότε διορθώσεις στην αναδημοσίευση, ευελπιστώ να τις βάλει στα επόμενα άρθρα της τότε σειράς ή φυσικά να του τις ξαναδώσω...

Η αρχική ιδέα της σειράς ήταν η χρήση metaprogramming στην C++, τόσο σε επίπεδο templates αλλά και σε επίπεδο preprocessor (κατ' αντιστοιχία βλέπε boost.preprocessor και πως γίνεται η χρήση του preprocessor για να δημιουργηθεί κώδικας - "κείμενο" ένα βήμα πριν την υπόλοιπη επεξεργασία αν και πρόκειται για απλά textual substitutions και πως γίνεται χρήση αυτού στην ίδια κατόπιν την boost.mpl). Όμως θα είχε τραβήξει σε αρκετά ειδικότερα θέματα που θα ήταν αδύνατο να εξαντληθούν στον τότε χώρο και για τον χρόνο που είχα διαθέσιμο. Και αν λάβουμε υπόψιν μας την νέα έκδοση της γλώσσας (C++11) και το constexpr metaprogramming, τότε πραγματικά ξεφεύγουμε. Έτσι κατά τη διάρκεια της περικοπής συγκέντρωσα το θέμα γύρω από το template metaprogramming και μόνον, αλλά λόγω χρόνου είχαν ξεφύγει κάποια πράγματα στο πρώτο άρθρο στο προφανέστερο τον οποίων αναφέρεσαι.

Τώρα, όσον αφορά το template metaprogramming (TMP) πιστεύω ότι συνεχίζει να είναι η πεμπτουσία της C++ και θα συνεχίσει να είναι για αρκετό καιρό. Είναι δύσκολο να βρείς κώδικα υψηλών απαιτήσεων από άποψη προσαρμοστικότητας και μεταβολής της συμπεριφοράς του χωρίς να θυσιάζεται η αποδοτικότητά του αν δεν υπεισέρχεται το συγκεκριμένο "style".

Σε λίγο καιρό ετοιμάζω κάτι πιό τραχύ σε επίπεδο C++11 για όσους ενδιαφέρονται το οποίο δεν θα έχει θεμιτούς περιορισμούς και θα έχει άνεση φυσικά στο proof - reading και με πάρα μα πάρα πολύ κώδικα.

Γιώργος.

ΥΓ. Δημητράκη, άλλη φορά και τις διορθώσεις...  θα σε sefgault - άρω!