Pagina 1 di 2 1 2 ultimoultimo
Visualizzazione dei risultati da 1 a 10 su 12
  1. #1

    [C++] Subscript operator overloading (lvalue/rvalue)

    Come si implementa correttamente l'overload dell'operatore []?

    Sembra che al mio compilatore piaccia decisamente la mia implementazione del lvalue, perchè l'altra non la prende mai in considerazione. Dove sbaglio?

    codice:
    // Overloading dell'operatore [] (rvalue) 
    template <typename T>
    T CLASSE<T>::operator[]( const unsigned pos ) const throw(...)
    {
    	if( pos >= size )
    		throw std::out_of_range( "Out Of Range exception occurs" );
    
    	return pointer[ pos ];
    }
    
    // Overloading dell'operatore [] (lvalue) 
    template <typename T>
    T & CLASSE<T>::operator[]( const unsigned pos ) throw(...)
    {
    	if( pos >= capacity )
    		throw std::out_of_range( "Out Of Range exception occurs" );
    
            return pointer[ pos ];
    }

    Usati come:

    codice:
    // lvalue
    oggetto[ i ] = value;
    
    //rvalue
    value = oggetto[ i ];

    Grazie
    Fracty - The Fractal Generator



    If you cannot choose a concise name that expresses what the method does, it is possible that your method is attempting to perform too many diverse tasks.

  2. #2
    Se non erro la versione const la chiama soltanto quando il this è const; prova a passare la tua classe ad una funzione tramite un reference costante, dovrebbe richiamare la versione const.

    Per inciso, non ha molto senso passare pos come const, in fin dei conti non è né un reference né un puntatore, per cui viene comunque copiato. Inoltre nella versione const di solito si usa restituire un riferimento const.
    Amaro C++, il gusto pieno dell'undefined behavior.

  3. #3
    Originariamente inviato da MItaly
    dovrebbe richiamare la versione const.
    Mi fai un esempio in cui questo:

    codice:
    value = oggetto[ i ]
    Si traduce nella chiamata alla versione rvalue?



    Originariamente inviato da MItaly
    Per inciso, non ha molto senso passare pos come const, in fin dei conti non è né un reference né un puntatore, per cui viene comunque copiato. Inoltre nella versione const di solito si usa restituire un riferimento const.
    Non ha senso se consideri unicamente l'aspetto pratico, ovvio che la copia che muore non ha la benchè minima importanza. Ma il principio del minor privilegio si applica spesso e volentieri anche in questi casi, l'informazzione aggiuntiva della non-modificabilità della variabile è resa più evidente da const, e non fa che migliorare la leggibilità del codice.
    Fracty - The Fractal Generator



    If you cannot choose a concise name that expresses what the method does, it is possible that your method is attempting to perform too many diverse tasks.

  4. #4
    Originariamente inviato da GliderKite
    Mi fai un esempio in cui questo:

    codice:
    value = oggetto[ i ]
    Si traduce nella chiamata alla versione rvalue?
    Sicuramente in questo caso:
    codice:
    void UnaFunzione(const CLASSE<int> & oggetto)
    {
        int unIntero=oggetto[i];
    }
    Per inciso, secondo me stai usando la terminologia rvalue/lvalue in maniera erronea, il fatto che un metodo sia const e che restituisca rvalue o lvalue sono due caratteristiche ortogonali (ad esempio, un riferimento const che può restituire la versione const è un const lvalue).
    Non ha senso se consideri unicamente l'aspetto pratico, ovvio che la copia che muore non ha la benchè minima importanza. Ma il principio del minor privilegio si applica spesso e volentieri anche in questi casi, l'informazzione aggiuntiva della non-modificabilità della variabile è resa più evidente da const, e non fa che migliorare la leggibilità del codice.
    Boh, semplicemente non avevo mai visto specificare il const su un parametro POD passato per copia né ne avevo mai sentito la necessità; comunque, male non fa, quindi non sarò certo io a dirti di toglierlo.
    Amaro C++, il gusto pieno dell'undefined behavior.

  5. #5
    Originariamente inviato da MItaly
    Sicuramente in questo caso:
    Capisco, c'è modo di richiamare il metodo indipendentemente dall'utilizzo della funzione a cui viene passato il riferimento costante?

    Come vedi c'è un minima seppur sostanziale differenza tra le due funzioni: si tratta della condizione per il lancio dell'eccezione. In questo modo quella differenza non avrebbe molto senso.
    Fracty - The Fractal Generator



    If you cannot choose a concise name that expresses what the method does, it is possible that your method is attempting to perform too many diverse tasks.

  6. #6
    Originariamente inviato da GliderKite
    Capisco, c'è modo di richiamare il metodo indipendentemente dall'utilizzo della funzione a cui viene passato il riferimento costante?
    Non credo che si possa fare direttamente. Credo che tu possa però farlo usando una classe proxy intermedia che abbia in overload l'operatore = (nel qual caso sarà destinazione di un assegnamento) e l'operatore di conversione implicita a T (nel qual caso si tratterà di una lettura). Non so però che overhead tu possa avere usando un trucco del genere.
    Amaro C++, il gusto pieno dell'undefined behavior.

  7. #7
    Esempio:
    codice:
    #include <iostream>
    #include <stdexcept>
    
    using namespace std;
    
    // Una classe Vettore a dimensione fissata
    template <typename T, unsigned int Size>
    class Vettore
    {
    private:
        T vec[Size];
        
        /*
           Versioni interne const e non-const
        
           La versione const verrà usata:
           - quando l'operatore [] viene usato sul lato destro di un assegnamento
           - in generale quando viene richiamata la versione const dell'operatore []
           
           La versione non-const verrà usata:
           - quando l'istanza è non-const e l'operatore [] viene usato sul lato
             sinistro di un assegnamento (se l'istanza è const teoricamente verrebbe
             richiamata la versione const, ma ovviamente in tal caso l'assegnamento
             non può aver luogo)
        */
             
        
        const T& internalGetItem(unsigned int Pos) const
        {
            std::clog<<"internalGetItem const"<<std::endl;
            // Qui implementa il codice per la versione const/rvalue
            if(Pos>=Size)
                throw std::out_of_range("Pos eccessivo.");
            return vec[Pos];
        }
        
        T& internalGetItem(unsigned int Pos)
        {
            std::clog<<"internalGetItem non-const"<<std::endl;
            // Qui implementa il codice per la versione non-const/lvalue
            if(Pos>=Size)
                throw std::out_of_range("Pos eccessivo.");
            return vec[Pos];
        }
    public:
        
        Vettore()
        {
            // Inizializzazione di default di tutti gli elementi del vettore interno
            for(unsigned int i=0; i<Size; i++)
                vec[i]=T();
        }
        
        // Classe proxy magica
        class SubscriptProxy
        {
        private:
            typedef Vettore<T, Size> ParentType;
            typedef T ElemType;
            // Classe genitore che andrà a richiamare per ottenere l'elemento
            ParentType & parent;
            // Posizione
            unsigned int pos;
            
            // Il costruttore non fa altro che inizializzare i campi
            // Viene tenuto privato per evitare che dall'esterno si possa istanziare
            SubscriptProxy(ParentType & Parent, unsigned int Pos) : parent(Parent), pos(Pos)
            {};
        public:        
            
            // Operatore di assegnamento (usato se la classe sta a sinistra di un
            // assegnamento) - richiama la versione non-const ed effettua
            // l'assegnamento tramite reference
            ElemType& operator=(const ElemType & NewValue)
            {
                std::clog<<"SubscriptProxy::operator=, pos="<<pos<<std::endl;
                return parent.internalGetItem(pos)=NewValue;
            }
            
            // Operatore di conversione implicita (usato se sta in un'espressione,
            // non alla sinistra di un uguale)
            operator const ElemType &() const
            {
                std::clog<<"SubscriptProxy::operator T, pos="<<pos<<std::endl;
                // Forza l'uso della versione const trasformando parent in un
                // riferimento const
                const ParentType & constParent = parent;
                return constParent.internalGetItem(pos);
            }
            
            // La classe genitore è friend così che la classe proxy possa accedere
            // ai suoi membri privati
            friend class Vettore<T, Size>;
        };
        
        // ... e viceversa (altrimenti la classe-genitore non potrebbe richiamare il
        // suo costruttore
        friend class Vettore<T, Size>::SubscriptProxy;
        
        // Versione const dell'operatore [] - semplicemente chiama la versione const
        // di internalGetItem
        const T& operator[](unsigned int Pos) const
        {
            return internalGetItem(Pos);
        }
        
        // Versione non-const dell'operatore [] - restituisce una nuova istanza
        // dell'oggetto-proxy, dicendole la posizione richiesta e dandole un
        // riferimento all'istanza della classe padre a cui andrà poi a chiedere
        // l'oggetto da restituire
        SubscriptProxy operator[](unsigned int Pos)
        {
            return SubscriptProxy(*this, Pos);
        }
    };
    
    // Prova con un riferimento costante (chiama la versione const di operator[])
    void Test(const Vettore<int, 20> & Vec)
    {
        cout<<Vec[0]<<endl;
    }
    
    int main()
    {
        // Crea il nostro vettore
        Vettore<int, 20> v;
        // Assegnamento semplice
        v[5]=10;
        // Rilettura - nota che qui viene chiamato operator[] non-const, ma grazie
        // alla classe proxy viene chiamata la versione const di internalGetItem
        int i=v[5];
        cout<<i<<endl;
        // Assegnamento leggendo anche il valore restituito
        cout<<(v[0]=20)<<endl;
        // Rilettura (direttamente nel cout, l'operatore di conversione implicita
        // viene comunque scelto in maniera corretta)
        cout<<v[0]<<endl;
        // Passo come riferimento costante
        Test(v);
        return 0;
    }
    Purtroppo un problema di questo approccio è che non è possibile fare cose come
    codice:
    v[0]++;
    v[0]+=50;
    e compagnia; non si possono neanche implementare gli operatori in questione manualmente nella classe proxy, dato che a quel punto la compilazione fallirebbe se il contenitore fosse usato su tipi che non implementano gli operatori su cui questi si basano.
    Anche roba di questo genere
    codice:
    int * ptr=&v[0];
    ha comportamento peculiare: non restituisce l'indirizzo dell'elemento in questione, ma dell'oggetto-proxy temporaneo; in effetti per ottenere l'indirizzo dell'elemento bisognerebbe fare
    codice:
    int * ptr = & static_cast<const int &>(v[0]);
    Amaro C++, il gusto pieno dell'undefined behavior.

  8. #8
    Originariamente inviato da MItaly
    Purtroppo un problema di questo approccio è che non è possibile fare cose come
    codice:
    v[0]++;
    v[0]+=50;
    e compagnia; non si possono neanche implementare gli operatori in questione manualmente nella classe proxy, dato che a quel punto la compilazione fallirebbe se il contenitore fosse usato su tipi che non implementano gli operatori su cui questi si basano.
    Anche roba di questo genere
    codice:
    int * ptr=&v[0];
    ha comportamento peculiare: non restituisce l'indirizzo dell'elemento in questione, ma dell'oggetto-proxy temporaneo; in effetti per ottenere l'indirizzo dell'elemento bisognerebbe fare
    codice:
    int * ptr = & static_cast<const int &>(v[0]);
    Si, stavo notando. Grazie per l'ottimo esempio.
    Fracty - The Fractal Generator



    If you cannot choose a concise name that expresses what the method does, it is possible that your method is attempting to perform too many diverse tasks.

  9. #9
    Originariamente inviato da GliderKite
    Si, stavo notando. Grazie per l'ottimo esempio.
    Di niente, mi diverto sempre a scrivere cose del genere. Forse dovrei imparare l'Haskell, dove la ridefinizione degli operatori è portata a livelli estremi.
    Amaro C++, il gusto pieno dell'undefined behavior.

  10. #10
    Per inciso, ho chiesto su StackOverflow se esiste qualche altro modo, ma mi è stato risposto che i proxy object sono l'unica via e comunque (come si vede dall'esempio) risulta impossibile rendere il trucco completamente trasparente al chiamante.
    Amaro C++, il gusto pieno dell'undefined behavior.

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2025 vBulletin Solutions, Inc. All rights reserved.