Προς το περιεχόμενο

Προτεινόμενες αναρτήσεις

  • Απαντ. 43
  • Δημ.
  • Τελ. απάντηση

Συχνή συμμετοχή στο θέμα

Δημοφιλείς Ημέρες

Δημοσ.

Το μόνο που αλλάζει στην hit είναι το όνομα της μπάλας στο cout, γι' αυτό το είπα. Πώς θα το έκανες εσύ;

 

Δεν είναι ίδια η λογική αν παρατηρήσεις καλά, κάποιες μπάλες έχουν διαφορετικό rate με το οποίο κάνουν regen και decay ενώ η Basketball δε κάνει regen καθόλου στη rest.

 

Επίσης όπως το έθεσες του δίνεις να καταλάβει ότι ο "τύπος" της μπάλας ορίζεται από το string, οπότε με if στον κώδικα της κλάσσης Ball θα προσδιορίζει τι πρέπει να κάνει στην εκάστοτε περίπτωση. 

 

Προσωπικά θα έκανα κάτι τέτοιο:

#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <ctime>

class Ball {

    int currDurability;
    
    int maxDurability;
    
    bool hidden;

    public:
    
    virtual ~Ball() {
        
    }
  
    void set(int maxDurability) {
        this->currDurability = maxDurability;
        this->maxDurability = maxDurability;
        this->hidden = false;
    }

    void hit() {

         std::cout << getName() << " ball is going to be hit!\n";

         if(hidden){
             std::cout << "You cannot hit a hidden ball\n";
             return;    
         } 

         std::srand(std::time(0));
         int i = 1 + std::rand()%5;

         if( i == 5 ) {
             hidden = true;
             std::cout << "Ball disappeared!\n";
         } else {

             currDurability -= getDurabilityDecay();
             std::cout << "Tsaf!\n";

             if(currDurability == 0) {
                std::cout << "The ball is about to break!\n";
             } else if(currDurability < 0) {
                std::cout << "Plof!\n";
             }
         }
    }

    void rest() {

        if(regenerates()) {
            
            currDurability += getDurabilityRegen();

            currDurability = std::min(currDurability, maxDurability);
            
        }

        std::cout << getName() << " is " <<  currDurability << '\n'; 
    }

    
    protected:
    
    Ball(int maxDurability)
    : currDurability(maxDurability),
      maxDurability(maxDurability),
      hidden(false) {
    }
    
    virtual const char* getName() = 0;

    virtual bool regenerates() {
        return false;
    }
   
    virtual int getDurabilityRegen() {
        return 0;
    }

    virtual int getDurabilityDecay() {
        return 0;
    }
};

class TennisBall : public Ball {

    public:

    TennisBall(int maxDurability) 
    : Ball(maxDurability) {
    }

    virtual ~TennisBall() {
    }
    
    protected:

    virtual const char* getName() {
        return "Tennis";
    }
    
    virtual bool regenerates() {
        return true;
    }

    virtual int getDurabilityRegen() {
        return 3; //καλύτερα να είναι constant της κλάσσης αλλά βαριέμαι 
                  //αντίστοιχα και στ'άλλα.
    }

    virtual int getDurabilityDecay() {
        return 5;
    }
};

class PingPongBall : public Ball {

    public:

    PingPongBall(int maxDurability) 
    : Ball(maxDurability) {
    }

    virtual ~PingPongBall () {
    }

    protected:
    
    virtual const char* getName() {
        return "PingPong";
    }
    
    virtual bool regenerates() {
        return true;
    }

    virtual int getDurabilityRegen() {
        return 1;
    }

    virtual int getDurabilityDecay() {
        return 1;
    }
};

class BasketBall : public Ball {

    public:

    BasketBall(int maxDurability) 
    : Ball(maxDurability) {
    }

    virtual ~BasketBall () {
    }

    protected:
    
    virtual const char* getName() {
        return "Basket";
    }
    
    virtual int getDurabilityDecay() {
        return 1; 
    }

};

 

Κάτι που μπορεί να άλλαζα θα ήταν να μην υπάρχουν μέθοδοι για τα regen rates αλλά private μεταβλητές στην base class οι οποίες θα ορίζονται από την subclass καρφωτά μέσω του constructor της. Αλλά αυτό είναι tradeoff μεταξύ χρήσης μνήμης και CPU time για το lookup των virtual μεθόδων. Tρίχες στη συγκεκριμένη περίπτωση, ο κώδικας πιστεύω πως είναι πιο καθαρός έτσι.

Δημοσ.

#include <iostream>

class Base {
public:  
    ~Base() {
        std::cout << "Base destructed" << std::endl;     
    }
};

class Derived: public Base {
    int _i;
public:
    ~Derived() {
        std::cout << "Derived destructed" << std::endl;     
    }
};

int main() {
    Base* p = new Derived();
    delete p;
    // Αφου o Destructor της Base ΔΕΝ ειναι virtual. το object p
    // γινεται 'sliced' αγνοωντας το Derived μερος του το οποιο
    // τωρα κανει leak!
    //
    // Δες τωρα τι γινεται αν κανεις τον  Destructor virtual ~Base()
    // Σε αυτη την περιπτωση δεν εχεις leak
}
Δημοσ.

Δε το λες και UB. Πχ, αυτο ειναι UB ? https://ideone.com/OuyvkB

Καταλαβαίνεις πως ποτέ δε μπορείς να αναγνωρίσεις την UB από το αποτέλεσμα οποιουδήποτε προγράμματος, μιας και αν αυτό είναι UB τότε μία από όλες τις πιθανές περιπτώσεις είναι να σου βγάλει αυτό που σου έβγαλε έτσι; :P

Δημοσ.

Γιατί διαφορετικά αν κάνεις αυτό είναι UB:

 

Base* p = new Derived();

delete p;

 

Επομένως classes που είναι για polymorphic use πάντα πρέπει να έχουν virtual destructor (τουλάχιστον αν αυτός είναι public).

Ναι. UB δεν νομίζω να είναι (δεν έχω τα specs πρόχειρα) αλλά καταλαβαίνω τι εννοείς.

Απλά, ήλπιζα να δώσει κάποια εξήγηση, γιατί ειδικά ο destructor. Δεν υπάρχουν άλλου είδους μέθοδοι που θα πρέπει οπωσδήποτε να είναι virtual; Ποιο έιναι το κριτήριο; (...το οποίο ισχύει σχεδόν πάντα σε ένα destructor; ;) )

 

Γιατί αυτό που έκανε εκεί είναι το "interface" στη C++ (κάνεις multiple inheritance αντί για "implements", αλλά μ' αυτό όπως είναι γλυτώνεις όλα τα προβλήματα του multiple inheritance). Interface = good.

 

Τα interfaces είναι καλά και άγια, αλλά για ποιο λόγο υπάρχει η ανάγκη multiple inheritance στο θέμα μας; Και αναφέρομαι τόσο στο OP όσο και στο παράδειγμα του kercyn.

Δημοσ.

Ναι. UB δεν νομίζω να είναι (δεν έχω τα specs πρόχειρα) αλλά καταλαβαίνω τι εννοείς.

Απλά, ήλπιζα να δώσει κάποια εξήγηση, γιατί ειδικά ο destructor. Δεν υπάρχουν άλλου είδους μέθοδοι που θα πρέπει οπωσδήποτε να είναι virtual; Ποιο έιναι το κριτήριο; (...το οποίο ισχύει σχεδόν πάντα σε ένα destructor; ;) )

Άπιστοι θωμάδες όλοι σας. :P

 

In the first alternative (delete object), if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined.

Όλες οι μέθοδοι που θα καλέσεις πολυμορφικά πρέπει να είναι virtual, αλλιώς δε θα έχεις βέβαια πολυμορφική συμπεριφορά. Ο destructor ειδικά γιατί αν τον καλέσεις χωρίς να έχει πολυμορφική συμπεριφορά είναι UB (ενώ στις άλλες περιπτώσεις είναι απλά άλλο θέλεις κι άλλο γίνεται).

 

Τα interfaces είναι καλά και άγια, αλλά για ποιο λόγο υπάρχει η ανάγκη multiple inheritance στο θέμα μας; Και αναφέρομαι τόσο στο OP όσο και στο παράδειγμα του kercyn.

Γιατί το "implements interface" στη C++ γίνεται αναγκαστικά με multiple inheritance.

  • Like 1
Δημοσ.

Οπως ειπαμε κανουμε virtual τον destructor για να αποφυγουμε το slicing στην περιπτωση που κανουμε delete

απο την βαση. To standard αναφερεται σε αυτο στην παραγραφο 12.4.12

 

Οταν γραφεις μια class πρεπει να εισαι προσεκτικος στο πως κανεις declare τον destructor.  Aν τον αφησεις non virtual, οι users της class σου, θα υποθεσουν οτι δεν πρεπει να κανουν derive απο αυτην. Για ενα καλο σχετικο παραδειγμα, δες την STL...  To std::vector ας πουμε, δεν εχει virtual desctuctor, αυτος και μονο ειναι ενας καλος λογος να μην την χρησιμοποιησεις σαν βαση.  Ενας λογος να αποφυγουμε virtual desctuctor ειναι το overhead που εχουμε με την προσθηκη του vtable...

 

Στην 11, Υπαρχει και keyword το οποιο μπορεις να χρησιμοποιησεις για να αποφυγεις τους users να κανουν derive απο μια κλαση σου (final) οπως κανεις στη java η C#. Αναλογο functionality ειχαμε και στην παλια C++ με ενα σχετικα αγνωστο idiom που χρησιμοποιουσε virtual inheritance...

 

Για να κανουμε implement ενα interface δεν ειναι αναγκαστικη η χρηση multiple inheritance

Δημοσ.

Καμία σχέση, το σωστό quote είναι αυτό που έδωσα παραπάνω και είναι από την 5.3.5/3. Η 12.4/12 περιγράφει το πού ψάχνει ο compiler να βρει τον operator delete() που θα καλέσει.

 

Για να κάνεις implement ένα interface δε χρειάζεσαι multiple inheritance αν το interface είναι ένα, και αν ταυτόχρονα δεν έχεις ήδη base class. Στη γενική περίπτωση χρειάζεσαι. Αλλά έτσι κι αλλιώς αυτό είναι καθαρά τεχνικό μιας και αν το interface είναι όπως πρέπει να είναι (χωρίς members δηλαδή και μόνο virtual functions) τότε αφ' ενός ο compiler μπορεί να κάνει EBO και αφ' ετέρου δεν έχεις και προβλήματα με το dreaded diamond οπότε είσαι όσον αφορά τα bits & bytes πρακτικά σα να μην έχεις κάνει multiple.

Δημοσ.

Όλες οι μέθοδοι που θα καλέσεις πολυμορφικά πρέπει να είναι virtual, αλλιώς δε θα έχεις βέβαια πολυμορφική συμπεριφορά. Ο destructor ειδικά γιατί αν τον καλέσεις χωρίς να έχει πολυμορφική συμπεριφορά είναι UB (ενώ στις άλλες περιπτώσεις είναι απλά άλλο θέλεις κι άλλο γίνεται).

Και στις δύο περιπτώσεις άλλο θέλεις κι άλλο γίνεται. Απλά στον destructor τα αποτελέσματα είναι ...destructive. B)

Mόνο στους destructors όμως; Και σε όλους;

 

Γιατί το "implements interface" στη C++ γίνεται αναγκαστικά με multiple inheritance.

Ναι βρε αγόρι μου, το καταλάβαμε όλοι. Άλλο ρωτάω:

 

...αλλά για ποιο λόγο υπάρχει η ανάγκη multiple inheritance στο θέμα μας;

Δημοσ.

Και στις δύο περιπτώσεις άλλο θέλεις κι άλλο γίνεται. Απλά στον destructor τα αποτελέσματα είναι ...destructive. B)

Mόνο στους destructors όμως; Και σε όλους;

 

Ε απο κάτω μου λες το καταλάβαμε αλλά εδώ πάλι τα ίδια θα πούμε...

 

Μόνο στους destructors και σε όλους τους destructors γιατί έτσι λέει το standard στο quote που έδωσα παραπάνω.

 

Στο θέμα μας δεν υπάρχει "ανάγκη" για multiple inheritance. Απλά συνήθως όπου interfaces και multiple inheritance γιατί έτσι καταλήγει στην πράξη.

Δημοσ.

Καμία σχέση, το σωστό quote είναι αυτό που έδωσα παραπάνω και είναι από την 5.3.5/3. Η 12.4/12 περιγράφει το πού ψάχνει ο compiler να βρει τον operator delete() που θα καλέσει.

 

Για να κάνεις implement ένα interface δε χρειάζεσαι multiple inheritance αν το interface είναι ένα, και αν ταυτόχρονα δεν έχεις ήδη base class. Στη γενική περίπτωση χρειάζεσαι. Αλλά έτσι κι αλλιώς αυτό είναι καθαρά τεχνικό μιας και αν το interface είναι όπως πρέπει να είναι (χωρίς members δηλαδή και μόνο virtual functions) τότε αφ' ενός ο compiler μπορεί να κάνει EBO και αφ' ετέρου δεν έχεις και προβλήματα με το dreaded diamond οπότε είσαι όσον αφορά τα bits & bytes πρακτικά σα να μην έχεις κάνει multiple.

 

Δεν νομιζω οτι διαφωνουμε..

Δημοσ.

Ε απο κάτω μου λες το καταλάβαμε αλλά εδώ πάλι τα ίδια θα πούμε...

Αυτό ήταν για τη σχέση interfaces και multiple inheritance. Νόμιζα ότι ήταν σαφές.

 

Μόνο στους destructors και σε όλους τους destructors γιατί έτσι λέει το standard στο quote που έδωσα παραπάνω.

Και γιατί η C++ σε αφήνει να δηλώνεις nonvirtual destructors; Για να έχεις leaks;

Αφού η πολυμορφική συμπεριφορά στους destructors είναι τελικά υποχρεωτική!!! Δεν καταλαβαίνω...

Αναφέρουν τα standards κάτι σχετικό;

 

Στο θέμα μας δεν υπάρχει "ανάγκη" για multiple inheritance. Απλά συνήθως όπου interfaces και multiple inheritance γιατί έτσι καταλήγει στην πράξη.

Με κάλυψες.

Δημοσ.

Και γιατί η C++ σε αφήνει να δηλώνεις nonvirtual destructors; Για να έχεις leaks;

Αφού η πολυμορφική συμπεριφορά στους destructors είναι τελικά υποχρεωτική!!! Δεν καταλαβαίνω...

Αναφέρουν τα standards κάτι σχετικό;

 

 Οπως ειπαμε παραπανω, ο non virtual destructor ειναι ελαχιστα πιο efficient αφου παραλειπει τον v-table.

Δημοσ.

 Οπως ειπαμε παραπανω, ο non virtual destructor ειναι ελαχιστα πιο efficient αφου παραλειπει τον v-table.

 

Ναι αλλά θα έχεις leaks! (συ είπας, και σε πιστεύω).

Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε

Πρέπει να είστε μέλος για να αφήσετε σχόλιο

Δημιουργία λογαριασμού

Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!

Δημιουργία νέου λογαριασμού

Σύνδεση

Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.

Συνδεθείτε τώρα
  • Δημιουργία νέου...