Newsgroup: it.comp.lang.c++
Titolo: come memorizzare le interfacce?

ciao a tutti,
ho un dilemma da proporvi: supponiamo di avere una classe che deve
memorizzare al suo interno un paio di interfacce che vengono passate,
diciamo, nel costruttore. Voi che meccanismo utilizzate? reference
(siano essi const o non const), puntatori nudi e crudi o qualche forma
di smart pointer (auto_ptr o shared_ptr o chicchessia).
La prima soluzione mi sembra causare solo problemi, la seconda č
quella piů semplice ma anche questa pone dei dubbi (i soliti problemi
che si hanno con i puntatori). La terza sembrerebbe quella giusta, ma
per esempio dichiarare un costruttore tipo:
classe (boost::shared_ptr <interfaccia> const&);
č una sofferenza... magari con un typedef? e dove li dichiarate i
typedef, nell'header della classe client, della classe server o in un
altro separato? Mi interessano soprattutto pratiche che usate in
codice reale.
-- 
Simone

 un paio di interfacce che vengono passate
Che tipo di interfacce? 
-- 
Luc.


scusami, intendo classi con almeno una funzione membro virtuale pura.
--------------------------------
Inviato via 
http://usenet.libero.it

 codice reale.
Se intendi comunque pubblicare delle interfacce astratte, da
utilizzare esclusivamente tramite shared_ptr, tanto vale che il
typedef sia nello header dove e' definita l'interfaccia. Se alla fine
risulta macchinoso puoi valutare se dichiarare shared_ptr senza
includerlo, ma non vedo perche' visto che comunque dovra' essere
incluso da chiunque usera' la tua interfaccia.
Ciao,
Nicola

Il 03 Apr 2003, 11:09, Nicola....@ObjectWay.it (Nicola Musatti) ha
scritto:

--------------------------------
Inviato via 
http://usenet.libero.it

 di smart pointer (auto_ptr o shared_ptr o chicchessia).
Tutto dipende dall'uso che ne deve essere fatto. (Adoro esordire con
considerazioni geniali.)
Le prime due sono sostanzialmente equivalenti, salvo il fatto che un
reference deve essere inizializzato nel costruttore e un puntatore può
cambiare valore.
Sulla terza va fatta una puntualizzazione: un auto_ptr non è
normalmente considerato uno "smart pointer", in quanto l'ownership non
può essere condivisa.
L'uso di un auto_ptr o di qualche genuino smart pointer (come quelli
di boost), ha implicazioni importanti:
 - l'oggetto associato dovra essere allocato con new
 - il suo lifetime sarà controllato unicamente dall'auto_ptr o dalle
varie instanze dello smart pointer
In questi casi, *io* di solito non uso smart pointer o auto_ptr.
Internamente uso normalmente un puntatore e adotto questo protocollo:
se il riferimento all'oggetto viene passato nel costruttore, il codice
client deve assumere che l'oggetto potrà essere usato anche nel
distruttore, altrimenti se il riferimento viene passato tramite metodi
appositi, il codice client può assumere che il distruttore non
cercherà di usarlo. Questa convenzione torna comoda quando si deve
stabilire l'ordine di distruzione dei vari oggetti coinvolti.
Max

 Che tipo di interfacce?
Nell'altra mia risposta, ho assunto che Simone intendesse
sostanzialmente "classi base astratte". Sono queste che vengono
solitamente chiamate "interfacce" in C++.
Max

 sostanzialmente "classi base astratte"
Corretto. Ma non so perchč mi era venuto subito in mente un OT (tipo 
interfacce COM et simila)... :) 
-- 
Luc.

 stabilire l'ordine di distruzione dei vari oggetti coinvolti.
Ok, deduco quindi che allochi il più possibile gli oggetti sullo stack. Ma
quando mi ritrovo che l'oggetto client (quello che usa l'ABC) deve
sopravvivere allo scope della funzione (esempio: una funzione factory che
crea un oggetto) questo *non* si può fare. In quel caso usi comunque un
puntatore nudo e crudo?



Tutto dipende dalla relazione di contenimento che vuoi adottare.
Il fatto che queste interfacce vengano passate in fase di costruzione,
presuppone appunto che il loro life time sia superiore a quello dell'
oggetto creato, che quindi non dovrebbe aver nessuna responsabilita' sul
loro ciclo di vita.
Quindi un reference dovrebbe essere sufficiente, ed esplicativo per un
client.
--
Ivan Molella
ICQ: 162760950

 client.
qui invece non mi trovo d'accordo, e ti spiego i miei motivi, che poi
derivano tutti dal fatto che un reference non può essere lasciato
non-inizializzato e non può essere riassegnato.
1- niente default ctor, quindi non posso inserire istanze di tale classe in
un container tipo SC++L.
2- niente operatore di assegnamento, necessario anch'esso per poter
utilizzare i container SC++L (per esempio, ma non solo).
Per contro, non vedo nessun vantaggio nell'uso dei reference.


Per contro, non vedo nessun vantaggio nell'uso dei reference.
Questo a dire il vero capita spesso :D ... se pero' lo dice
un programmatore C tutti i programmatori C++ iniziano a
picchiarlo con grossi randelli.
Andrea

 picchiarlo con grossi randelli.
be', intendo nel contesto del mio post iniziale. Come parametro di ingresso
ad una funzione (sia essa membro oppure no - ctor escluso -) li vedo bene
per due motivi:
1. '.' al posto di '->' : un carattere di meno ;). Vantaggio trascurabile.
2. so per certo che quel parametro esisterà. Se vedo un puntatore devo,
almeno, verificare che quel puntatore non sia nullo.


 Per contro, non vedo nessun vantaggio nell'uso dei reference.
Come ho detto sopra, dipende dalla relazione di contenimento che vuoi
implementare, o comunque sia piu' in generale dall' architettura delle tue
classi.
Il fatto che tu voglia prendere un riferimento od un puntatore ad un oggetto
esterno, cozza un pochino con il mantenere tale riferimento in un
contenitore all'interno della tua classe.
Forse l'approccio piu' corretto sarebbe quello di una factory o di un Holder
come suggerito da Griffini, o comunque sia si deve presupporre un
implementazione dell' oggetto referenziato che tenga conta della sua natura
shared, e che quindi puo essere utilizzato anche come reference;
Quella che riporto sotto e' l' ipotetica implementazione di un oggetto
"Manager" di tipo shared:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
class manager {
public:
 manager():a_RefCount(0),a_IsAClone(true){
  a_RefCount = new int(0);
  a_String = new char[255];
  strcpy(a_String,"manager original\n");
  std::cout << "Sono un manager:" << (*a_RefCount) << std::endl;
 }
 manager(const manager& copy):
 a_RefCount(copy.a_RefCount),a_IsAClone(copy.a_IsAClone){
  if (copy.a_IsAClone){
   std::cout << "Sono un clone di manager." << std::endl;
   a_RefCount = new int(0);
   a_String = new char[255];
   strcpy(a_String,"manager original copy\n");
  }
  else{
   a_String = copy.a_String;
   (*a_RefCount)++;
   std::cout << "Sono un referenced clone di manager: " << (*a_RefCount) <<
std::endl;
  }
 }
 ~manager(){
  if (!(*a_RefCount)){
   delete [] a_String;
   delete a_RefCount;
   fprintf(stdout,"manager destroyed!. %p\n",a_String);
  }else{
   (*a_RefCount)--;
   fprintf(stdout,"manager not destroyed!. %p\n",a_String);
  }
 }
 char* getMessage(){
  return a_String;
 }
 static manager makeAManagerRef(manager& copy){
  copy.a_IsAClone = false;
  manager retCopy = manager(copy);
  copy.a_IsAClone = true;
  return retCopy;
 }
 virtual char* getMessage() const{
  return a_String;
 }
 virtual void doFoo(){
  std::cout << "doFoo() invoked." << std::endl;
 }
 static manager& doDefaultManager();
private:
 int* a_RefCount;
 bool a_IsAClone;
 char *a_String;
};
class notAValidManager : public manager {
public:
 notAValidManager(){
  std::cout << "Sono un manager non utilizzabile." << std::endl;
 }
 notAValidManager(const notAValidManager& copy){
  std::cout << "Sono una copia di manager non utilizzabile." << std::endl;
 }
 void doFoo(){
  std::cout << "throw Exception(Metodo non supportato!)" << std::endl;
 }
 char* getMessage() const{
  return "Not a Valid Message!!!";
 }
};
manager& manager::doDefaultManager(){
  static notAValidManager ret = notAValidManager();
  return ret;
 }
class user {
public:
 user():a_Mgr(manager::doDefaultManager()){
  std::cout << "****************" << std::endl;
  std::cout << "Sono un user default ctor." << std::endl;
  a_Mgr.doFoo();
  a_Vec.push_back(manager::makeAManagerRef(a_Mgr));
  a_Vec.push_back(a_Mgr);
  std::cout << ">>>>>Manager message: " << a_Vec[0].getMessage() <<
std::endl;
  std::cout << ">>>>>Manager message: " << a_Vec[1].getMessage() <<
std::endl;
 }
 user(const user& copy):a_Mgr(copy.a_Mgr){
  std::cout << "****************" << std::endl;
  std::cout << "Sono una user copy ctor." << std::endl;
  a_Mgr.doFoo();
  a_Vec.push_back(manager::makeAManagerRef(a_Mgr));
  a_Vec.push_back(a_Mgr);
  std::cout << ">>>>>Manager message: " << a_Vec[0].getMessage() <<
std::endl;
  std::cout << ">>>>>Manager message: " << a_Vec[1].getMessage() <<
std::endl;
 }
 user(manager& pManager):a_Mgr(pManager){
  std::cout << "****************" << std::endl;
  std::cout << "Sono un user." << std::endl;
  a_Mgr.doFoo();
  a_Vec.push_back(manager::makeAManagerRef(a_Mgr));
  a_Vec.push_back(a_Mgr);
  std::cout << ">>>>>Manager message: " << a_Vec[0].getMessage() <<
std::endl;
  std::cout << ">>>>>Manager message: " << a_Vec[1].getMessage() <<
std::endl;
 }
private:
 vector<manager> a_Vec;
 manager& a_Mgr;
};
int main(int argc,char *argv[])
{
manager t_Mgr;
user t_User1(t_Mgr);
user t_User2;
}

 shared, e che quindi puo essere utilizzato anche come reference;
senz'altro è correttissimo, ma è anche alquanto pesante. client,
interfaccia, implementazione dell'interfaccia e factory. Conto almeno
quattro classi, e servirà generalmente anche uno smart pointer. 5 classi.
Alquanto pesantino mi pare...
<snip codice>
sembra tanto COM :). Comunque ho capito il tuo approccio: fondamentalmente
il client non sa che ha a che fare con puntatori, reference o quant'altro
perchè la classe factory (o holder, come la vuoi chiamare) crea un ulteriore
livello di indirezione. Ottimo approccio, senz'altro, ma preferirei evitare
qualcosa di così 'barocco', considerato che sviluppo pure in un ambiente
'malsano' (compilatore scadente e, soprattutto, risorse limitate).


 'malsano' (compilatore scadente e, soprattutto, risorse limitate).
Il problema e' sempre quello, le cose barocche sono in genere la strada piu'
lunga, pero' l'idea e' che se strutturi bene i pezzi di codice di base e non
opti per soluzioni "tattiche" che il giorno dopo devi buttar via, te lo
ritroverai sia nell'ambito dello stesso progetto che magari in altri
contesti.

non sia nullo.
Sul fatto che si possa scrivere codice che usa una classe
guardando solo il prototype senza commenti e senza
informazioni di contesto sono molto molto dubbioso.
I reference possono comunque essere associati a oggetti
inesistenti (un po' come i dangling pointer) e quindi da
quel punto di vista non sono molto piu' sicuri.
Il fatto che rendano meno "evidente" l'indirezione al
contrario secondo me tende ad aumentare il rischio di
usi errati (es. aliasing).
HTH
Andrea

 noto che a volte l'effetto e' vicino all'opposto :(
Queste tue parole mi evocano sentimenti contrastanti. Da una parte
sembrano uno sfogo momentaneo difficilmente condivisibile (le
definizioni sono parte di ciò che ci consente di comunicare concetti e
le "anguste caselline" rappresentano una delle più alte attività umane,
la modellizzazione), dall'altra paiono evidenziare profonde verità (è
giusto che l'informatica diventi produzione industriale, divenendo più
efficiente ma forse meno ricca di idee, o è preferibile che resti,
almeno in parte, arte creativa dei programmatori?).
EMG
--
EMAG Software Homepage:     
http://www.emagsoftware.it
The EMG's ZX-Spectrum Page: 
http://www.emagsoftware.it/spectrum
The Best of Spectrum Games: 
http://www.emagsoftware.it/tbosg

 usi errati (es. aliasing).
l'aliasing capita anche con i puntatori, e non lo rendono certo più
evidente.


 è una sofferenza... magari con un typedef? e dove li dichiarate i


resti, almeno in parte, arte creativa dei programmatori?).
Io non sono un sostenitore della visione industriale
dell'informatica, sono piu' per la visione creativa e,
purtroppo, poco pianificabile. Nonostante questo ritengo
che alcuni strumenti "industriali" siano molto interessanti
(e che altri siano assolutamente risibili).
Andrea

evidente.
Secondo me con i reference la cosa e' piu' subdola, in
particolare per colpa della consuetudine che dice che usare
const reference e' un modo furbo di passare valori.
Un caso reale in cui mi sono imbattuto e' il seguente...
   struct Point
   {
     int x,y;
     Point(int x,int y) 
       : x(x),y(y) {}
     Point& operator+=(const Point& other)
       { x+=other.x; y+=other.y; return *this; }
     Point& operator-=(const Point& other)
       { x-=other.x; y-=other.y; return *this; }
   };
   struct Rect
   {
     Point tl,br;
     Rect(const Point& tl,const Point& br) 
       : tl(tl),br(br) {}
     Rect& operator+=(const Point& delta)
       { tl+=delta; br+=delta; return *this; }
     Rect& operator-=(const Point& delta)
       { tl-=delta; br-=delta; return *this; }
   };
   ...
   void Window::foo()
   {
     Rect r = get_rect(); // window rect in coordinate screen
     r -= 
r.tl
;           // porta in coordinate locali (NO!)
     ...
   }
Forse dipende dal mio passato di programmi C... ma lo
statement "tl-=delta;" e' secondo me veramente poco
sospetto. Eppure morde.
Non so se questo "nascondere" che la variabile delta
in realta' e' una mascheratura di una indirezione e'
sempre una buona cosa.
Andrea

 Esiste un nome per questa roba ? Sicuro... anzi... probabilmente
I 4 matacchioni lo chiamerebbero un "flyweight".

 e' invece secondo me una attivita' particolarmente elevata.
Concordo con quanto dici........comunque i mattacchioni erano 4.......:):)

 (e che altri siano assolutamente risibili).
Sono d'accordo.


 sospetto. Eppure morde.
Scusa, mi puoi spiegare esattamente il problema? Senza compilare non
riesco a rendermi conto di dove si nasconda.


riesco a rendermi conto di dove si nasconda.
Scrivendo "r -= 
r.tl
" succede che l'istruzione
"tl -= delta" modifica giustamente tl portandolo
a 0, ma anche delta per colpa dell'aliasing
(evidentemente non voluto in questo caso); quindi
la sottrazione successiva non modifica br come
invece dovrebbe e il risultato e' che invece di
traslare il rettangolo si modifica solo il topleft.
La cosa brutta e' proprio che il codice sembra a
prima vista corretto... (in parte grazie anche alla
"feature" che offrono i reference di nascondere
completamente nella sintassi l'indirezione che il
loro uso comporta).
Andrea

 possibilita' di usare "puntatori logici".
Già, purtroppo. Riguardando il codice con il senno di poi ti direi che un
puntatore logico, in questo caso (proprio una stringa sarebbe andata
benissimo), mi avrebbe risolto un sacco di problemi.. buono per la prossima
volta.

una cosa tipo
r.subtract (&
r.tl
);
ti sarebbe apparsa più sospetta?




Il problema non e' nella chiamata r -= 
r.tl
 ... ma
nell'implementazione della traslazione del rettangolo.
Scrivendo
 
r.tl
 -= delta;
 
r.br
 -= delta;
non si tiene conto del fatto che il primo statement
puo' cambiare delta. Una implementazione corretta
potrebbe semplicemente dichiarare la traslazione
con
    Rect& operator-=(Point delta)  // Per valore!
e il problema non sarebbe presente.
Lo statement
 
r.tl
 -= *delta;
 
r.br
 -= *delta;
lascia (leggermente) piu' visibile il fatto che delta
puo' cambiare tra una riga e la successiva.
Secondo me un problema dei reference e' che la sintassi
tende a nascondere l'indirezione (e non sempre questo e'
un aiuto) e soprattutto che viene propagandata la pratica
di passare dei "const reference" per ottimizzare quando
quello che logicamente e' richiesto e' un valore.
Ovviamente questa ultima parte non centra nulla con il tuo
caso originale in cui il fatto che tu volessi una indirezione
verso un altro oggetto preesistente era assolutamente chiaro.
Questa pratica del passaggio indiscriminato di const
reference e' molto radicata... al punto che non so quanti
di fronte a quella dichiarazione di Rect::operator-=()
avrebbero qualcosa da obbiettare. Tu pensa che io ho fatto
ben peggio che non obbiettare... quella dichiarazione
sbagliata l'avevo scritta di mio pugno :DDD
Andrea

 traslare il rettangolo si modifica solo il topleft.
Ti chiederei troppo se ti chiedessi un piccolo esempio compilabile che
mostri il problema? Solo in questo modo potrei capirlo perfettamente.


 puo' cambiare delta.
Ora ho capito! Siccome delta, essendo passato per riferimento, è r.t1
allora scrivere
r.tl
 -= delta;
equivale a scrivere
r.t1 = r.t1 - r.t1;
Dico bene?


Dico bene?
Esatto... e l'istruzione succesiva che fa 
r.br
 = 
r.br
 - 
r.tl
non ha l'effetto voluto perche' ora delta vale (0,0).
Andrea