User Tools

Site Tools


cpp:casting

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
cpp:casting [2026/04/24 11:53] – [Παράδειγμα 2ο: Κληρονομικότητα (Upcasting & Downcasting)] gthanoscpp:casting [2026/04/24 12:28] (current) – [reinterpret_cast<>] gthanos
Line 99: Line 99:
  
 <WRAP tip> <WRAP tip>
-Χρησιμοποιήστε static_cast μόνο όταν η μετατροπή τύπου είναι απόλυτα λογική (π.χ. <code> double d = static_cast<double>(5); </code>) ή είναι σίγουρο ότι το casting δεν θα αποτύχει σε καμία περίπτωση.+Χρησιμοποιήστε **static_cast** μόνο όταν  
 +  * η μετατροπή τύπου είναι απόλυτα λογική, για παράδειγμα  
 +<code> double d = static_cast<double>(5); </code>  
 +  * είναι σίγουρο ότι το casting δεν θα αποτύχει σε καμία περίπτωση. Για παράδειγμα, 
 +<code>  
 +Derived derived(100); 
 +Base& b = static_cast<Derived&>(derived);  
 +</code> 
 + 
 +Το **static_cast** εφαρμόζεται σχεδόν στα πάντα: 
 + 
 +  * **Κανονικούς Τύπους:** Μπορείς να μετατρέψεις βασικούς τύπους μεταξύ τους (π.χ. double σε int, float σε char). 
 +  * **Pointers:** Μετατροπή μεταξύ δεικτών σε μια ιεραρχία κλάσεων (Base* σε Derived* και αντίστροφα). 
 +  * **References**: Λειτουργεί ακριβώς όπως και με τους pointers για αναφορές σε κλάσεις. 
 +  * **Enums:** Μετατροπή enums σε integers και αντίστροφα. 
 + 
 +**Σημαντικό:** Στο **static_cast**, αν η μετατροπή αναφορών (references) αποτύχει λογικά (π.χ. το αντικείμενο δεν είναι αυτό που νομίζεις), το πρόγραμμα θα συνεχίσει να τρέχει με λάθος δεδομένα (undefined behavior), γιατί δεν υπάρχει έλεγχος στο runtime
 </WRAP> </WRAP>
  
-====== dynamic_cast<> ======+===== dynamic_cast<> =====
  
 +Το **dynamic_cast** είναι ο τύπος casting που εφαρμόζεται στον πολυμορφισμό. Το χρησιμοποιούμε όταν έχουμε έναν δείκτη προς μια βασική κλάση (Base*) και εξετάζουμε αν το αντικείμενο στο οποίο δείχνει είναι μια συγκεκριμένη παράγωγη κλάση (Derived*).
  
 +Ακολουθεί ένα σενάριο από ένα σύστημα πληρωμών, όπου έχουμε διαφορετικούς τύπους τραπεζικών λογαριασμών. Έχουμε μια βασική κλάση **Account** και δύο παράγωγες: **SavingsAccount** (που έχει επιτόκιο) και **CheckingAccount** (επαγγελματικός λογαριασμός όψεως, χωρίς επιτόκιο). Θέλουμε να γράψουμε μια συνάρτηση που δέχεται οποιονδήποτε λογαριασμό, αλλά εφαρμόζει τόκο μόνο αν ο λογαριασμός είναι αποταμιευτικός.
  
-====== const_cast<> ======+<code cpp account_polymorphism.cpp> 
 +#include <iostream> 
 +#include <vector> 
 + 
 +// Η Base κλάση πρέπει να είναι πολυμορφική (να έχει τουλάχιστον μία virtual function) 
 +class Account { 
 +public: 
 +    virtual void withdraw(double amount) { 
 +        std::cout << "Ανάληψη " << amount << " από τον βασικό λογαριασμό." << std::endl; 
 +    } 
 +    virtual ~Account() {} // Απαραίτητος ΠΑΝΤΑ ο virtual destructor 
 +}; 
 + 
 +class SavingsAccount : public Account { 
 +public: 
 +    void applyInterest() { 
 +        std::cout << "Εφαρμογή επιτοκίου στον αποταμιευτικό λογαριασμό!" << std::endl; 
 +    } 
 +}; 
 + 
 +class CheckingAccount : public Account { 
 +public: 
 +    void printStatement() { 
 +        std::cout << "Εκτύπωση κίνησης λογαριασμού όψεως." << std::endl; 
 +    } 
 +}; 
 + 
 +void processAccount(Account* acc) { 
 +    // Θέλουμε να καλέσουμε την applyInterest(), αλλά αυτή υπάρχει ΜΟΝΟ στην SavingsAccount. 
 +    // Χρησιμοποιούμε dynamic_cast για να ελέγξουμε με ασφάλεια. 
 +     
 +    SavingsAccount* savings dynamic_cast<SavingsAccount*>(acc); 
 + 
 +    if (savings != nullptr) { 
 +        // Η μετατροπή πέτυχε! Το acc δείχνει όντως σε SavingsAccount. 
 +        savings->applyInterest(); 
 +    } else { 
 +        // Η μετατροπή απέτυχε. Το acc είναι κάτι άλλο (π.χ. CheckingAccount). 
 +        std::cout << "Αποτυχία cast: Αυτός ο λογαριασμός είναι επαγγελματικός." << std::endl; 
 +    } 
 +
 + 
 +int main() { 
 +    // Δημιουργούμε ένα vector από διαφορετικούς λογαριασμούς (Base pointers) 
 +    std::vector<Account*> bank_vault; 
 +    bank_vault.push_back(new SavingsAccount()); 
 +    bank_vault.push_back(new CheckingAccount()); 
 + 
 +    for (Account* acc : bank_vault) { 
 +        processAccount(acc); 
 +        delete acc; 
 +    } 
 + 
 +    return 0; 
 +
 +</code> 
 + 
 +<WRAP tip 80% center round> 
 + 
 +  * **Runtime Check:**  Το dynamic_cast χρησιμοποιεί μια πληροφορία που ονομάζεται RTTI (Run-Time Type Information). Όταν καλείται η συνάρτηση processAccount, ο compiler δεν ξέρει τι περιέχει η μεταβλητή acc. Η απόφαση παίρνεται την ώρα που εκτελείται το πρόγραμμα. 
 +  * **Ασφάλεια (Safety):** Αν χρησιμοποιούσαμε **static_cast** και το αντικείμενο ήταν τύπου CheckingAccount, ο pointer savings θα ήταν έγκυρος (αλλά θα έδειχνε σε λάθος δεδομένα). Η κλήση savings->applyInterest() θα οδηγούσε σε crash. Το dynamic_cast μας επιστρέφει **nullptr** και μας σώζει. 
 +  * **Πολυμορφισμός:** Πρόσεξε ότι η Account έχει //virtual// συναρτήσεις. Χωρίς αυτές, το dynamic_cast θα έβγαζε σφάλμα στη μεταγλώττιση, γιατί δεν θα υπήρχε πίνακας vtable για να ελέγξει τον τύπο. 
 +  * **Δε λειτουργεί για κανονικούς τύπους:** Δεν μπορείς να κάνεις dynamic_cast<int>(my_double). Θα πάρεις σφάλμα κατά τη μεταγλώττιση. Το **dynamic_cast** απαιτεί ο τύπος προορισμού να είναι δείκτης ή αναφορά σε τύπο κλάσης. 
 + 
 +</WRAP> 
 + 
 +**Συνοπτικός Πίνακας** 
 + 
 +^  Τύπος  ^  static_cast  ^  dynamic_cast 
 +^ Βασικοί Τύποι (int, double, κλπ)  | Ναι  | Όχι 
 +^ Pointers (Base* -> Derived*)  | Ναι (χωρίς check)  | Ναι (επιστρέφει nullptr σε αποτυχία) 
 +^ References (Base& -> Derived& | Ναι (χωρίς check)  | Ναι (πετάει std::bad_cast σε αποτυχία) 
 +^ Enums  | Ναι  | Όχι  |   
 + 
 + 
 +===== const_cast<> ===== 
 + 
 +Είναι ο μόνος τελεστής που μπορεί να προσθέσει ή να αφαιρέσει το const (ή το volatile) από μια μεταβλητή. 
 + 
 +  * **Γιατί να το κάνεις:** Συνήθως χρησιμοποιείται όταν έχεις να κάνεις με παλιές βιβλιοθήκες (Legacy C code) που δέχονται char* ενώ ξέρεις ότι δεν πρόκειται να τροποποιήσουν το περιεχόμενο, αλλά εσύ έχεις ένα const char*. 
 +  * **Ο Κίνδυνος:** Αν αφαιρέσεις το const από μια μεταβλητή που ορίστηκε εξ αρχής ως const και προσπαθήσεις να την αλλάξεις, το αποτέλεσμα είναι Undefined Behavior (μπορεί να "σκάσει" το πρόγραμμα ή να μην αλλάξει η τιμή ποτέ). 
 + 
 +<code cpp cost_cast.cpp> 
 +void legacy_function(char* str) {  
 +   /* ... */  
 +
 + 
 +const char* my_text "Hello"; 
 +// legacy_function(my_text); // ΣΦΑΛΜΑ 
 +legacy_function(const_cast<char*>(my_text)); // Επιτρέπεται 
 +</code>
  
 ====== reinterpret_cast<> ====== ====== reinterpret_cast<> ======
 +
 +Αυτός είναι ο πιο ισχυρός και επικίνδυνος τελεστής. Λέει στον compiler: "Κάνε αυτή τη μετατροπή, ακόμα και αν δεν βγάζει νόημα".
 +
 +**Τι κάνει:** Μετατρέπει οποιονδήποτε δείκτη σε οποιονδήποτε άλλον τύπο δείκτη, ή ακόμα και δείκτη σε ακέραιο και το αντίστροφο.
 +
 +**Προσοχή:** Δεν ελέγχει τίποτα. Αν μετατρέψεις έναν int* σε Dog* και καλέσεις μια μέθοδο, ο compiler θα το κάνει και απλώς θα προσπαθήσει να διαβάσει το περιεχόμενο του int** σαν να ήταν αντικείμενο της κλάσης Dog.
 +
 +<code cpp reinterpret_cast.cpp>
 +long address = 0x7FFF1234;
 +// Ερμήνευσε αυτόν τον αριθμό ως διεύθυνση μνήμης ενός ακεραίου
 +int* p = reinterpret_cast<int*>(address);
 +</code>
 +
 +Ο συγκεκριμένος τελεστής μοιάζει με το type casting που γνωρίζουμε από τη γλώσσα C, διότι ο μεταγλωττιστής ακολουθεί χωρίς έλεγχο τις οδηγίες του προγραμματιστή.
 +
 +
cpp/casting.1777031635.txt.gz · Last modified: 2026/04/24 11:53 by gthanos