Newsgroup: it.comp.lang.c++
Titolo: Operatore []

Ciao a tutti.
Se ho una classe tipo questa:
class ProvaStringa{
private:
        char *s;
public:
        ProvaStringa(int n);    // Alloca una stringa di lunghezza n
        ProvaStringa(const char *s);    // Vabbè, si capisce! :-)
        ~ProvaStringa();
        char operator [](int n);// Ritorna l'n-simo carattere della stringa
};
e faccio qualcosa tipo:
ProvaStringa p("Prova");
cout<<p[3]<<endl;
tutto bene.
Se invece volessi fare:
p[3]='a';
ovvero sostituire il 4° carattere della mia stringa con una bella 'a', come 
dovrei fare? (naturalmente lasciandoma la possibilità di fare quello che ho 
scritto sopra!) 
Ridefinire l'operatore = ? E come?
Certo, se facessi un membro tipo:
        char SostituisciCarattere(int n,char c){ s[n]=c; return c;}
andrebbe benissimo, ma lo sfizio è proprio quello di fare quello che ho 
scritto sopra!
Chi mi aiuta? Grazie.


ad occhio e croce, (non provo a compilarlo, ma sfrutta l'idea)
dovresti avere due operatori [] (uno const e uno non const)
qualche cosa del tipo:
const char operator[](unsigned int) const;
char& operator[](unsigned int);
Buona fortruna.
Ciao
Gaetano.
-- 
Posted from 
dell-01.mm.eutelsat.org
 [194.214.173.17] 
via Mailgate.ORG Server - 
http://www.Mailgate.ORG


Devi fare in modo che operator[] ritorni un lvalue (ossia un oggetto
che sia modificabile). Ossia deve restituire una referenza:
    char& operator[](int n) { return s[n]; }
Per evitare che oggetti ProvaString costanti si possano
modificare, devi fare un overload di operator[] per ProvaString
costante. Ossia:
    char operator[](int i) const { return s[n]; }
Nota:
in questo caso ho restituito una copia di un char, per
oggetti più grandi si può restituire una referenza costante:
    const Tipo& operator[](int i) const;
Ciao   Manlio Perillo

 p[3]='a';
1) Pericolo!! La tua classe non ha il copy operator, per cui te lo crea
di default. Ma dato che allochi e disallochi memoria rischi forte di
trovarti nei casini.
2) manca anche il copy constructor. Sicuro che non ti serve?
3) dichiara (e implementa) questo metodo:
char& operator [](int n);
e poi ANCHE uno di questi due (quello che preferisci, ma il primo č
leggermente meglio IMO):
char operator [](int n) const;
const char& operator [](int n) const;
--
Andrea Ferro
---------
Brainbench C++ Master. Scored higher than 97% of previous takers
Scores: Overall 4.46, Conceptual 5.0, Problem-Solving 5.0
More info 
http://www.brainbench.com/transcript.jsp?pid=2522556

const char& operator [](int n) const;
Perché il primo è meglio secondo te? Motivi di prestazioni? E se non
ci fosse un semplice char ma un qualcosa di + grande?
-- 
Marco Barisione (ICQ 84277092)
Su it.news.gruppi si sta discutendo della creazione di it.comp.lang
dove si parlerà dei linguaggi senza un proprio newsgroup e dei
confronti fra i diversi linguaggi.
Se sei interessato unisciti alla discussione.

 ci fosse un semplice char ma un qualcosa di + grande?
Sono quasi la stessa cosa. In generale se l'oggetto è piccolo considero
preferibile il passaggio per valore quando possibile (vale anche per i
parametri, non solo per i valori di ritorno).
Naturalmente la preferenza si basa su considerazioni veramente
soggettive e che non hanno vero fondamento se i compilatori sono scritti
bene e se gli utenti si comportano bene (e si dovrebbe sempre partire da
questa ipotesi).
In teoria il "tempo" necessario a ritornare il valore (ricorda che parlo
di oggetti piccoli e senza costruttori, come appunto un char) è lo
stesso necessario a ritornare un reference.
Il tempo usato dal chiamante per accedere al risultato, se il
compilatore è scritto bene, è uguale, se non è scritto bene può essere
leggermente inferiore nel caso di ritorno per valore (ma se non è
scritto bene potrebbe anche essere il contrario!!).
L'utente non può forzare un uso non previsto (una const reference è
passibile di const_cast, un valore no).
Ma forse la considerazione più importante è un'altra: considera le
implicazioni su codice multithreaded. La reference potrebbe essere
salvata e usata più volte purchè si sia certi della lifetime
dell'oggetto. Ma se questo viene fatto, in caso di multithreading ti
trovi con problemi grossi di sincronizzazione. Lavorando per valore, in
caso di multithreading hai maggiore località in termini di necessità di
sincronismo.
Ripeto: nel caso specifico è praticamente la stessa cosa. Ma mentre
fatico a trovare un motivo perchè una futura evoluzione del metodo da
"char metodo() const" a "const char& metodo() const" possa generare
problemi nel chiamante, non faccio fatica a vedere casi in cui il
chiamante funzionava con la reference e non funziona più con il valore.
Quindi preferisco vietare a priori che venga scritto codice che funziona
solo con la reference e non con il valore imponendo il valore. Se poi ci
sarà un motivo valido per cambiare, almeno sapro' che il codice
preesistente continua a funzionare.


 sincronismo.
In realta' il problema si pone, per quanto meno drammaticamente, anche
nel codice single threaded. Come si fa a ricordarsi quali reference
memoriziamo in giro per il codice? Quando si torna liberi di distruggere
l'oggetto al cui interno punta il reference?
Ciao,
Nicola

 l'oggetto al cui interno punta il reference?
E' responsabilita' del chiamante infatti usare un oggetto passato per
reference
e' buttarlo subito via, se te lo conservi (sotto forma di reference)
sai a quali rischi vai in contro.
Considerando che il piu' delle volte memorizziamo gli oggetti all'interno
di container STL e sai benissimo cosa puo succedere ad ogni push_back (ad
esempio).


 esempio).
Io risolvo il problema non passando mai un sotto-oggetto per reference
(ovvero di fatto restituisco per reference solo "*this")
Ciao,
Nicola



Anche *this puo essere pericoloso, a meno che non sia una istanza static.


 esempio).
In realtà il mio discorso era più inteso in senso logico.
Se la reference viene salvata in un container e mantenuta per tempo
indeterminato il problema dovrebbe diventare evidente. Ma ci sono anche
altri casi. Supponi che il valore di ritorno venga passato ad una
funzione. Per esempio:
void bar(const C&c) {
    ...
    foo(c.mem());
    ...
}
ora C::mem() può tornare un const reference o un oggetto by-value. Se si
tratta di un tipo la cui semantica è by-value (come uno scalare e la
maggior parte dei tipi "piccoli"), il ritorno by-value è meno
problematico. Infatti foo potrebbe essere un template per motivi di
generalità. Potrebbe quindi mantenere la reference per periodi
relativamente lunghi. Ovviamente in caso di passaggio a multithreading a
noi non interessa molto se "c" cambia stato durante l'esecuzione di foo
(altrimenti avremmo usato un lock). Ma ci si può aspettare che mem,
essendo un accessor, sia atomico (o internamente sincronizzato). Si
suppone che l'algoritmo foo però funzioni molto meglio se il parametro
non gli cambia sotto il naso!
In altre parole, supponendo di non sapere cosa succederà in futuro a
quel tipo di ritorno,  non vedo casi in cui cambiarlo da by-value a
by-const-ref crei problemi sul codice esistente ma ci sono casi (non
quello di questo esempio) in cui il contrario avviene. Quindi meglio
dichiararlo by-value in prima istanza e cambiarlo solo se serve (e
dubito che servirà mai).


 (ovvero di fatto restituisco per reference solo "*this")
E' una buona norma. Ma non generalizzabile ai container.


 Anche *this puo essere pericoloso, a meno che non sia una istanza
static.
Non dovrebbe esserlo poi molto dato che il chiamante è certamente già in
possesso di quell'indirizzo/reference.



E se avesse un oggetto locale o peggio lo avesse in un vector ?
Per me il piu' sicuro (ma non totalmente sicuro) e' il reference di un
oggetto statico.


 oggetto statico.
Non ho capito. Supponiamo la classe:
class GetMe {
public:
    GetMe* me() { return this; }
    const GetMe* me() const { return this; }
};
Fammi un esempio in cui il chiamante di GetMe::me non ha accesso al
valore ritornato dal metodo anche prima di chiamarlo.


 E' una buona norma. Ma non generalizzabile ai container.
Mi sono accorto che prima non sono stato esatto: intendevo dire che non
ritorno mai un sotto-oggetto per reference; se posso ritorno per valore
oppure uso un puntatore, piu' o meno intelligente a seconda dei casi.
Ciao,
Nicola

 valore ritornato dal metodo anche prima di chiamarlo.
class ACollectionOfGetME
{
    std::vector<GetMe> theCollection;
    void addGetMe(GetMe aGetMe) {
        theCollection.push_back(aGetMe);
    }
    void foo(void) {
        GetMe* myGetMe = 0;
        if (theCollection.size()>=10) {
            myGetMe = theCollection[10].me();
        }
 
       addGetMe(GetMe());
       //qui la variabile myGetMe e' ancora valida?
    }
}; 
NB. Non ho provato, ma credo che ci possano essere problemi.
Ciao
Gaetano.


 valore ritornato dal metodo anche prima di chiamarlo.
o ancora meglio:
    GetMe* myGetMe = GetMe().me();
    //e da qui in poi cosa ne e' del mio myGetME?


     //e da qui in poi cosa ne e' del mio myGetME?
E qual e' la differenza rispetto a
    GetMe * myGetMe = & GetMe();
A meno che l'operatore & non si implementato in maniera "non intuitiva",
il metodo me() che ritorna this non da nessun "valore aggiunto" alla classe.
Fabio
-- 
Posted from 
flopiano.mm.eutelsat.org
 [194.214.173.28] 



Infatti e' lo stesso. Ritornare *this (per reference) o this (per puntatore)
non e' indice di garanzia o meglio senza stare a fare i salti mortali
e piu' pericoloso che dare un reference o un puntatore di un
oggetto statico.
Ciao
Gaetano.
-- 
Posted from 
dell-01.mm.eutelsat.org
 [194.214.173.17] 


     }
Naturalmente la risposta dipende da addGetMe, nota però che il problema
non sta nel fatto che me ritorna this. Dentro l'if tu avevi già un
reference all'oggetto. Avresti potuto scrivere "myGetMe =
&theCollection[10];". Se lo fai hai lo stesso genere di problema. La
presenza di GetMe::me non ti ha dato nulla di nuovo.


 oggetto statico.
Calma!
La questione originale era una affermazione che ritornare un reference
ad un oggetto contenuto era pericoloso in quanto può dare al chiamante
un accesso che non gli compete violando l'encapsulation e qualcuno
(credo fosse Nicola) disse che lui ritornava al massimo *this. Al che
c'è statu un reply che anche ritornare *this può essere pericoloso, il
che sottintende che anche ritornare *this (o this) può dare al chiamante
un accesso che il chiamante non dovrebbe avere. Io sollevo un dubbio a
quest'ultima affermazione.
Il primo esempio di Gaetano (altro post, relativo ad un container) non
regge perchè l'accesso all'indirizzo dell'oggetto invocato è comunque
presente prima della chiamata.
Il secondo esempio è molto meglio.
GetMe* myGetMe = GetMe().me();
questo codice effettivamente da accesso ad un indirizzo che non si
potrebbe ottenere se non chiamando un membro.
Fabio chiede quale sia la differenza rispetto a
GetMe* myGetMe = &GetMe();
E Gaetano replica che è la stessa cosa.
Se fosse la stessa cosa allora la presenza del membro non darebbe al
chiamante nessun accesso che il chiamante non avrebbe comunque (come nel
caso del container). Purtroppo non è affatto la stessa cosa. Il codice
GetMe* myGetMe = &GetMe();
non compila (o meglio non compila su un compilatore conforme) perchè
l'operatore & richiede un l-value. Sembrerebbe quindi che la presenza
del metodo dia effettivamente un accesso che il chiamante non avrebbe.
In realtà non è del tutto vero. Il chiamante non ha accesso diretto
all'oggetto come l-value a meno che non invochi un metodo. In altre
parole la creazione esplicita del temporaneo si comporta in maniera
diversa a seconda di come la si usa. Questo fatto è stato discusso e
criticato più volte.
Ti faccio notare, però, che per qualunque oggetto assegnabile potresti
scrivere:
GetMe* myGetMe = &GetMe().operator=(GetMe());
che è valido a meno che tu non abbia definito l'operator= privato o
protetto.
Però stiamo veramente andando a parlare di codice "forzato".
Ci ho pensato ed esiste un solo caso in cui i metodi che ritornano this
o *this possono dare al chiamante un accesso che altrimenti non avrebbe.
Ed è il caso in cui il chiamante chiama il metodo tramite una istanza di
una classe che implementa operator->


 una classe che implementa operator->
Forse non ho capito cosa intendi, ma ahime' nessuno ti impedisce di fare
una cosa come:
struct A {
  A & me() { return *this; }
};
class B {
  public:
    B() : a(new A) {}
    A * operator ->() { return a; }
  private:
    A * a;
};
int main() {
  B b;
  A & a = *(b.operator -> ());
}
Per quanto in modo contorto, l'accesso l'avevi gia' anche in questo
caso!
Ciao,
Nicola
P.S. Qualche volta l'information hiding sta nel non far sapere ai
colleghi che si possono fare queste cose :-)

 colleghi che si possono fare queste cose :-)
ROTFL. Vero. Però non puoi applicarlo a questo NG altrimenti tanto vale
chiudere bottega!
Però mi sovviene un piccolo particolare ... per il quale faccio partire
un altro thread.



Grazie