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

[C++] const function που καλεί non-const function


Kercyn

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

  • Moderators

Καλησπέρα σας και καλή χρονιά.

 

Υποθέστε ότι έχουμε την παρακάτω κλάση:

class Test final
{
    public:
        RandomClass* RC;
 
        void ChangeSomething(int NewValue) const
        {
                RC->ChangeSomething(NewValue)
        }
};

όπου το RandomClass::ChangeSomething είναι non-const. Είναι "λάθος" το Test::ChangeSomething να είναι const; Δεν αλλάζει την τιμή του RC, αλλά καλεί μια non-const συνάρτηση που αλλάζει αυτό στο οποίο δείχνει το RC. Δεν υπάρχει κανένα θέμα με λάθη κλπ, απλώς τώρα που ξανακοιτάω κάποιες τέτοιες κλάσεις μου γεννήθηκε αυτή η απορία. Εσείς αν βλέπατε την Test::ChangeSomething, θα σας φαινόταν "κακό" ή παράξενο να έχει μια τέτοια συμπεριφορά; Δεν είναι ότι καλεί μια άσχετη non-const συνάρτηση ενός άλλου τυχαίου αντικειμένου, καλεί non-const συνάρτηση ενός δικού της αντικειμένου.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Κακό, πολύ κακό. Στον παρακάτω κώδικα η doSthElse() θα επέστρεφε πλέον κάτι διαφορετικό σε σχέση με πριν, που σημαίνει ότι η κατάσταση της Test έχει αλλάξει.

class Test final
{
    public:
        RandomClass* RC;
 
        void ChangeSomething(int NewValue) const
        {
                RC->ChangeSomething(NewValue)
        }
        
        void doSthElse() 
        {
                return RC->getSomething();
        }

};

const method πρέπει να καλεί μονάχα const methods των members της κλάσης στην οποία ανήκει.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Λάθος είναι να κάνεις πράγματα που δεν είναι "logically const", και μόνο αυτό. Δεν είναι απαραίτητο να κάνεις μόνο πράγματα που είναι "physically const".

 

Υπάρχει τρόπος μέσα από το public interface της Test, άμεσα ή έμμεσα να καταλάβει κανείς τι έκανε η ChangeSomething? (Ισοδύναμα, αν κάνεις observe το αντικείμενο Test πριν την κλήση της ChangeSomething() εξωτερικά, υπάρχει τρόπος μετά από λίγο να καταλάβεις αν έχουμε καλέσει την ChangeSomething() ή όχι?) Αν ναι, τότε είναι λάθος. Αν όχι, κανένα πρόβλημα (εξάλλου γι' αυτές ακριβώς τις περιπτώσεις δημιουργήθηκε και το mutable).

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

  • Moderators

Οι κλάσεις αυτές χρησιμοποιούνται ως wrappers για να μπορούν να έχουν πρόσβαση διάφορα lua scripts σε λειτουργίες του προγράμματός μου. Αυτή είναι η μόνη τους χρησιμότητα, δεν διαχειρίζονται τίποτα στο πρόγραμμα. Οι μέθοδοί τους καλούνται μόνο από τη lua, από πουθενά μέσα στο πρόγραμμα. Γι' αυτό και όλες οι μέθοδοι αυτών των κλάσεων είναι const (με ελάχιστες εξαιρέσεις που όντως αλλάζουν κάτι μέσα στην ίδια την κλάση), επειδή είναι αυτό που είπε ο defacer "physically const", δηλαδή δεν αλλάζουν κάτι μέσα στο ίδιο το αντικείμενο. Μετά απ' αυτά που λέτε βέβαια (και έχετε δίκιο), θα βγάλω το const από συναρτήσεις που δεν είναι const στο wrapped αντικείμενο.

 

Ευχαριστώ!

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Μετά απ' αυτά που λέτε βέβαια (και έχετε δίκιο), θα βγάλω το const από συναρτήσεις που δεν είναι const στο wrapped αντικείμενο.

Υπόψη ότι προσωπικά δεν είπα αυτό έτσι; Αν το wrapped αντικείμενο δε μπορεί να επηρεάσει τη συμπεριφορά του wrapper τότε το αφήνεις κιόλας το const.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

  • Moderators

Υπόψη ότι προσωπικά δεν είπα αυτό έτσι; Αν το wrapped αντικείμενο δε μπορεί να επηρεάσει τη συμπεριφορά του wrapper τότε το αφήνεις κιόλας το const.

 

Χμμ, τότε κατάλαβα λάθος. Το wrapped αντικείμενο δεν επικοινωνεί καθόλου με το wrapper, και ο wrapper απλώς κρατάει έναν pointer στο wrapped αντικείμενο. Ο wrapper απλώς καλεί μέσω του pointer συναρτήσεις του wrapped αντικειμένου. Ο λόγος ύπαρξής τους είναι ότι η Lua δέχεται/επιστρέφει συγκεκριμένα πράγματα και δε μπορώ πχ να επιστρέψω ένα glm::vec2 (2D διάνυσμα) στη Lua, πρέπει να επιστρέψω ένα std::tuple<LUA_NUMBER, LUA_NUMBER> και άλλα τέτοια.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Χμμ, τότε κατάλαβα λάθος. Το wrapped αντικείμενο δεν επικοινωνεί καθόλου με το wrapper, και ο wrapper απλώς κρατάει έναν pointer στο wrapped αντικείμενο. Ο wrapper απλώς καλεί μέσω του pointer συναρτήσεις του wrapped αντικειμένου. Ο λόγος ύπαρξής τους είναι ότι η Lua δέχεται/επιστρέφει συγκεκριμένα πράγματα και δε μπορώ πχ να επιστρέψω ένα glm::vec2 (2D διάνυσμα) στη Lua, πρέπει να επιστρέψω ένα std::tuple<LUA_NUMBER, LUA_NUMBER> και άλλα τέτοια.

 

Θα ήταν χρήσιμο να φέρεις ένα παράδειγμα γιατί μια const method του wrapper να αναγκάζεται να καλέσει μια non-cost του wrapped, και if in doubt απλά βγάλε το const (καλύτερα να μη δίνεις εγγυήσεις που θα μπορούσες παρά να παραπλανήσεις κάποιον), αλλά γενικά κράτα αυτό που είπα για logical vs physical const. Το logical αρκεί.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

  • Moderators

Έχεις δίκιο γι' αυτό με το παράδειγμα. Κάθε φορά προσπαθώ να κάνω μια ερώτηση που είναι πιο γενική από κάτι που αντιμετωπίζω για να το δω πιο σφαιρικά και γενικά αλλά πάντα χρειάζεται παράδειγμα :P

 

Έχουμε λοιπόν αυτή την κλάση (μέρος της):

class AActor
{
    public:
        const glm::vec2& GetPosition() const;
    
        void SetPosition(const glm::vec2& NewPosition);
        void SetPosition(float NewX, float NewY);

    protected:
        //! The actor's current position on the screen.
        glm::vec2 Position;
};

και έχουμε και το wrapper:

class SIActor final
{
    public:
        AActor* Actor;

        std::tuple<LUA_NUMBER, LUA_NUMBER> GetPosition() const;
        void SetPosition(LUA_NUMBER NewX, LUA_NUMBER NewY);
};

Το SIActor::SetPosition καλεί το AActor::SetPosition. Το SIActor δεν αλλάζει κάτι στο SIActor object, αλλά η κλήση στο Actor->SetPosition αλλάζει το Actor. Απ' όσο ξέρει το SIActor object, μετά την κλήση της SetPosition δεν έχει αλλάξει κάτι. Η διεύθυνση του Actor είναι αυτή που ήταν, οπότε όλα ωραία όλα καλά. Αλλά το αντικείμενο στη διεύθυνση που δείχνει το Actor έχει αλλάξει.

Αρχικά ακολουθούσα την πρώτη λογική και οι SI (Script Interface) κλάσεις είχαν const παντού (εκτός από μερικές εξαιρέσεις). Μετά όμως σκέφτηκα ότι θα ήταν πιο "λογικό" (αφού τα SI είναι wrappers) να ακολουθούν την const λογική των συναρτήσεων που καλούν από το pointed object (όπως το Actor).

Εκεί είπα να ζητήσω και μια δεύτερη γνώμη για το τι γίνεται και τι θα ήταν καλό να ακολουθείται σ' αυτές τις περιπτώσεις.

 

ΥΓ.: Στη Lua υπάρχουν γραμμές τύπου Player:SetPosition(x, y), όπου αυτές οι συναρτήσεις είναι bound με τις αντίστοιχες συναρτήσεις των SI. Η Lua ούτε μπορεί να δει ούτε ασχολείται με οτιδήποτε άλλο, η μόνη της επικοινωνία με το κυρίως πρόγραμμα είναι μόνο μέσω των SI functions

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Κοιτα, το const θα βαρεσει οταν γινει call μιας non-const function σε ενα const object. Σε εσενα το Actor δεν ειναι const, ετσι δεν υπαρχει λογος να βλαεις const.

 

Btw γιατι wrapper και οχι interface;

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

  • Moderators

Κοιτα, το const θα βαρεσει οταν γινει call μιας non-const function σε ενα const object. Σε εσενα το Actor δεν ειναι const, ετσι δεν υπαρχει λογος να βλαεις const.

 

Ναι δεν είχα θέμα με λάθη, πιο πολύ αναρωτιόμουν ποιο είναι το πιο "σωστό" σ' αυτές τις περιπτώσεις. const επειδή δεν αλλάζει κάτι στο object ή non-const επειδή το function που γίνεται wrap είναι non-const;

 

 

Btw γιατι wrapper και οχι interface;

 

Γιατί όπως βλέπεις τα signatures δεν είναι πάντα τα ίδια. Εκτός αν εννοείς κάτι άλλο.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

  • Moderators

Χρησιμοποιώ αυτό.

 

Του Actor συγκεκριμένα γίνεται έτσι (State είναι το Lua state)

 

State["PActor"].SetClass<SIActor, std::string>(
        "GetPosition", &SIActor::GetPosition,
        "GetKey", &SIActor::GetKey,
        "GetID", &SIActor::GetID,
        "GetProperty", &SIActor::GetProperty,
        "GetColliderID", &SIActor::GetColliderID,
        "IsEnabled", &SIActor::IsEnabled,
        "Translate", &SIActor::Translate,
        "Rotate", &SIActor::Rotate,
        "SetPosition", &SIActor::SetPosition,
        "SetRotation", &SIActor::SetRotation,
        "SetOrientation", &SIActor::SetOrientation,
        "SetProperty", &SIActor::SetProperty,
        "Enable", &SIActor::Enable,
        "Disable", &SIActor::Disable);
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

  • Moderators

Δε θα ήταν μπερδεμένα έτσι όμως; Και τι ακριβώς κερδίζω μ' αυτό, αφού θα πρέπει να υπάρχουν και οι δύο συναρτήσεις (αυτή πχ που επιτστρέφει glm::vec2 και η άλλη που επιστρέφει std::tuple).

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

εσυ πες μου.

typedef float lua_type;
class ILuaObject 
{
public:
	virtual void SetVal(const lua_type& val) = 0;
};

class Object : public ILuaObject
{
	int val;
public:
	void SetVal(const int& val)
	{
		this->val = val;
	}
	void ILuaObject::SetVal(const lua_type& val)
	{
		this->SetVal(static_cast<int>(val));
	}
};

Μπορεις να βαλεις και ενα perfix σε καθε function πχ si_SetVal

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

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

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

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

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

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

Σύνδεση

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

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