Newsgroup: it.comp.lang.c++
Titolo: classe Connection: approccio corretto

Qual è, secondo voi, l'approccio corretto (o comunque consigliabile)
nella creazione di una classe Connection?
Mi spiego: supponendo si tratti di una connessione a un database (ma
si può estendere il concetto a qualsiasi altro ambito), e volendo
sfruttare al massimo l'idioma RAII, è meglio attivare la connessione
all'atto della creazione dell'istanza? oppure lasciare la possibilità
di usare metodo connect() in un secondo momento?
Io cerco di sganciarmi dal vecchio approccio (tipicamente procedurale)
di dati + operazioni sui dati, per cui tendo a lavorare in base alle
entità. Il ragionamento che faccio è semplice: se una Connection è una
connessione e sto creando una Connection, allora devo effettivamente
creare una connessione, quindi connettermi.
Sto per iniziare la scrittura di un framework C++ attorno a un set di
funzioni C e mi interessa sapere come la pensate. Esistono,
eventualmente, discussioni/libri/articoli su problemi come questo?
Thanks!

 e aggiungere quindi i metodi per connettersi/sconnettersi.
Personalmente classi di questo tipo le scrivo sempre con il metodo separato.
Il motivo principale e` che ne facilita il riuso senza ricorrere 
all'allocazione dinamica.
Se derivo una classe da Connection o scrivo una classe che contiene un 
oggetto Connection sono costretto a "connettermi" alla creazione 
dell'oggetto derivato o del contenitore. In alternativa posso usare new 
(nel caso del container perlomeno), ma in tal caso devo gestirmi anche 
la deallocazione.
Si perde anche in flessibilita`, nel caso specifico in particolare 
potrebbe essere utile/efficiente portersi disconnettere e connettere ad 
un altro db senza distruggere / ricostruire l'oggetto, con un metodo di 
connect lo puoi fare.
Inoltre la creazione di una connessione in un costruttore richiede la 
gestione di un fallimento della connessione tramite exception, cosa che 
personalmente non mi piace perche` le exception nei costruttori, specie 
se ci sono classi derivate di mezzo, sono problematiche per la 
deallocazione delle risorse.
Bye,
  Gabry

 di usare metodo connect() in un secondo momento?
La tua domanda nasconde un presupposto che mi permetterei, però, di
discutere. Il presupposto è che la risorsa in questione possa (o debba
ragionevolmente) essere acquisita tramite una o più operazioni interamente
eseguite nello scope di una singola funzione. Solo in questo caso possiamo
chiederci se sia o no ragionevolmente subordinare la creazione del nostro
oggetto al fatto che detta funzione sia stata eseguita con successo. 
Ora, questo modo di acquisire (e usare) certi tipi di risorse, pur
largamente adoperato (perché dopo tutto semplifica concettualmente la
programmazione), preclude la possibilità di gestire le varie operazioni in
modo asincrono. 
In generale, nel mio piccolo, se devo disegnare nuove API, cerco, nei limiti
del possibile, di offrire all'utilizzatore la possibilità di agganciare le
attività I/O potenzialmenti bloccanti a un event loop (un demultiplexer, un
reactor, o come si voglia chiamarlo).
Se oggi dovessi progettare un API per accedere un database, forse cercherei
un approccio che possa integrarsi bene con l'impianto di Boost.Asio, non
solamente per quel che riguarda la fase di connessione, ma anche per quella
di interrogazione e di lettura dei dati.
Comprendo, comunque, che uno debba poi fare i conti con API e librerie
esistenti, che spesso prevedono una singola chiamata bloccante per la
connesione e l'apertura di una sessione con il database (a volte senza
nemmeno offrire la possiblità di specificare un timeout). Quindi, a conti
fatti, è ben possibile che tutto il mio sproloquio non aggiunga alcun
contributo significativo alla soluzione del tuo problema. :)
Max
PS: Consiglio, a chi non l'ha ancora fatto, di darsi uno sguardo a
Boost.Asio. Io me lo sto studiando nei tempi morti e sto valutando l'idea
di portare sotto Asio il codice che ho scritto con ACE. Il modello generale
di Asio è basato su concetti analoghi a quelli di ACE, ma l'approccio è
molto più moderno in termini di stile e costrutti adottati.

 di usare metodo connect() in un secondo momento?
...
Il fatto di sfruttare l'idioma RAII direi che è *indispensabile*, però 
non capisco perchè le due opzioni (raii / metodo connect()) siano per 
forza alternative!
Mi spiego meglio:
chi deve usare la connection, presumibilmente la utilizzerà in due 
"scenari":
1 - nel corpo di uno "stack frame" (e in questo caso fa comodo la 
connessione nel costruttore)
2 - come membro di un oggetto (e in questo caso potrebbe fare comodo un 
metodo connect esplicito)
pertanto IMHO l'approccio migliore è:
  - nel distruttore COMUNQUE rilascio la connessione
  - un costruttore senza parametri che non si connette
  - un costruttore con parametri che si connette
  - un metodo connect (e a questo punto anche disconnect) esplicito
unica rogna è che facendo così devi sempre controllare se la connessione 
è attiva o no (e se non lo è tiri un'eccezione o usi un ritorno 
speciale), mentre con l'approccio del solo costruttore "connesso" puoi 
dare per scontato che se una connection esiste allora è anche connessa.
Forse si potrebbe pensare di spezzare la cosa in due: una versione 
"leggera" con costruttore connettente e una "pesante" con metodo connect 
esplicito. Le due potrebbero essere imparentate o usare 
containment/delega... ma è solo un'idea buttata li
GM

contributo significativo alla soluzione del tuo problema. :)
Affatto, è preziosissimo invece e ti ringrazio tanto.
Comunque, per rispondere alle altre persone intervenute: una delle due
opzioni è proprio quella di usare un metodo separato connect() ed è, a
dire il vero, il tipo di soluzione a cui penso per prima, di solito. 
Discutendo con un collega, però, il problema si era spostato su cosa
sarebbe più giusto fare a livello puramente logico, nel senso che, se
una connessione viene rappresentata da una classe Connection e se
l'esistenza di una connessione significa, nella realtà, un link attivo
tra due punti, probabilmente anche l'esistenza di una Connection
dovrebbe implicarlo. 
Se sì, questo significherebbe che non dovrebbe essere possibile avere
Connection aggregate e "spente". Ovviamente, la conseguenza è che nel
caso serva una Connection in un istante qualsiasi, si faccia uso di
allocazione dinamica, cosa antipatica e costosa in termini di
performance. 
In sostanza, il principio è che se la connessione esiste, è attiva
necessariamente, perché ricalca il modello reale. 
Io e il mio collega discutevamo proprio dell'opportunità di ragionaree
in termini di dato (la Connection) + operazioni fattibili sul dato
(quindi connect() ecc...) oppure in termini di comportamento di
un'entità.
Non ci preoccupa molto il sollevamento di eccezioni nel costruttore,
perché la gestione corretta degli errori rientra in quell'ambito di
cure particolari che bisogna avere quando si progetta un sistema, né
ci preoccupa l'estensione per derivazione, perché le classi
apparterrebbero a un framework e sarebbero "finali" per l'utente.

Secondo me è più "comodo" disaccoppiare costruttore e metodo connect  in 
quanto l'uso di un metodo per la connessione ti permette 2 cose :
la prima è di connetterti e sconnetterti più volte con lo stesso oggetto
e magari condividerlo,
la seconda di gestire in maniera semplice in caso di errore , per 
esempio dopo un errore di rete una riconnessione per eseguire operazioni 
successive.
ciao
Pier Paolo
Andrea Laforgia ha scritto:


successive.
Grazie dei consigli. In realtà, il fatto di "connettersi e
sconnettersi" io lo vedo come esistenza e non esistenza di una
connessione. In termini di logica, distruggere un oggetto Connection
significa disconnettersi. Anche le riconnessioni sono semplicemente
nuove connessioni, intese in questo senso. Come vedi, è solo un
cavillo logico. Il fatto è che stiamo scrivendo un wrapper per
l'Oracle Call Interface e in fase di connessione bisogna allocare
almeno 4 handle (per una logica abbastanza contorta dell'API Oracle).
Se accade un errore sul metodo connect() è abbastanza fastidioso
andare a deallocare gli handle (il che implica anche controllare quali
sono allocati correttamente, ecc...). Preferivamo la strada della
connessione "autopartente" proprio per evitare questi problemi e
ridurre le possibilità di errore ed anche perché in questo modo si
sfrutta in pieno il RAII, visto che in fase di costruzione si
acquisiscono effettivamente le risorse che servono e si rilasciano in
fase di distruzione. Vedremo! :-)

 Voglio dire che non è proprio possibile connettersi in modo asincrono.
Leggo nell'altro tuo messaggio che state scrivendo un wrapper per la OCI.
Con l'OCI molte operazioni possono essere eseguite in modalità non-bloccante
(v. OCI_ATTR_NONBLOCKING_MODE). Non so se sia possibile anche con OCILogon.
Ad ogni modo, sono un po' curioso. Posso chiederti come mai dovete scrivere
un wrapper C++? 
Le opzioni C++ esistenti non vi soddisfano? La OTL, ad esempio, non vi
piace? E la OCCI? Ci sarebbero, poi, anche alcune librerie/API generiche
che supportano Oracle (come SQLAPI++ e SOCI).
Io uso OTL da diverso tempo, ormai.
Nicola Musatti stava lavorando su Boost.SqlCli, ma non vedo nuovi messaggi
sul suo blog. Mi piacerebbe sapere cosa pensa della mia idea di un
approccio basato su Boost.Asio.
Max

Personalmente preferisco Session come nome della classe e non
Connection
In questo modo è più evidente che una Session possa essere in stato di
connesso o non connesso (da cui i metodi Connect e Disconnect)
Se stai wrappando le OCI ti troverai quasi sicuramente a definire una
classe Statement che ha una referenza (o un puntatore) a Session (ad
esempio un membro di tipo OCIEnv in Session e un membro OCIStmt in
Statement)
Dato che la classe si chiama Session risulterà perfettamente lecito
avere oggetti Statement associati ad una Session non connessa.
(ovviamente sarà vietato usarli fintanto che la Session corrispondente
non viene connessa con successo)
Marco

 almeno 4 handle (per una logica abbastanza contorta del
Perch[ riscrivere una cosa che gia' esiste ? non puoi usare gli oracle
object for c++ ? sono gia pronti e fatti ... e secondo me comodi :) ....

object for c++ ? sono gia pronti e fatti ... e secondo me comodi :) ....
Secondo il mio collega sono tutt'altro che comodi, progettati male, e
lasciano troppe operazioni in sospeso nelle mani del programmatore.
Effettivamente è così.
p.s.: non avevi già risposto?

Il giorno lun, 19/05/2008 alle 22.36 +0200, Andrea Laforgia ha scritto:
Non avevo capito che stessi riscrivendo gli oracle object for c++. Io li
trovai comodi ... alla fine se hai un po di dimestichezza con jdbc ...
la struttura e` molto simile .... inoltre... hai il vantaggio che
aggiornando di volta in volta tutto il client oracle ricompili il tuo
codice senza doverti "preoccupare" di eventuali variazioni delle OCI
sottostanti... secondo me.... tra tutto ... il gioco non vale la
candela :)


Io li trovai comodi ... 
Ho già scritto che non li abbiamo trovati comodi.

> Ho già scritto che non li abbiamo trovati comodi.
Ma guarda, tanto per parlare a me farebbe paura solo il fatto che la
oracle mi possa cambiare sotto il sedere la API delle OCI.... per il
resto la scelta di riscriverla non la critico minimamente :) 
Pero` una situazione simile mi e` capitata con delle classi custom di
accesso a MQ.... in un contratto di AM abbiamo dovuto riscriverne buona
parte di ste classi custom poiche` mamma IBM aveva modificato alcune
API ... nulla di devastante ma all`inizio... trattandosi di software non
+ modificato dal 1999 ed a noi sconosciuto .... mi sono un po cagato in
mano :) (w l'application mantainence :) ) 
 

oracle mi possa cambiare sotto il sedere la API delle OCI.... per il
Si adeguerà il framework di conseguenza, lo scopo è proprio quello.
D'altronde i concetti fondamentali non cambierebbero.

approccio basato su Boost.Asio.
Spero anch'io che intervenga ;-)

beh, che le OCI cambino sono il naso lo vedo decisamente remoto!
le OCI valide per la 7 di oracle erano ancora valide fino all'altro
giorno (o magari lo sono ancora, non lo escludo)
voglio dire che fino ad ora (intendo dalla 7 in poi) ho visto oracle
estendere le oci o magari rinnovarle completamente (come nel caso di
Oracle 8) mantenendo tuttavia una piena retrocompatibilità
nel mio caso sono passato a chiamate nuove quando era utile sfruttare
nuove funzionalità: è stato così per usare i blob/clob, per unicode,
etc.
per quanto riguarda la scrittura di framework C++ di wrapping delle
OCI è la scelta che abbiamo fatto anche noi, condizionati anche da
motivi storici (quando siamo partiti non c'era niente di decente e già
pronto in C++ e le OCCI non esistevano neppure)
ad ogni modo la mia opinione è che il gioco vale la candela
soprattutto come occasione per capire bene le varie possibilità
progettuali che le OCI permettono
... in effetti i gradi di libertà che danno le OCI sono veramente
tanti e di conseguenza gli aspetti progettuali
Es. solo per citarne alcuni altri da tenere in conto:
- sei più orientato a degli statement sql definiti al compile time o
stringhe sql composte al run-time?
- più orientato a Statement "usa e getta" oppure statement riusati
milioni/miliardi di volte su cui ha senso fare la prepare una sola
volta all'inizio e poi chiamare il solo execute?
Tenendo conto che la "prepare" è il momento in cui oracle effettua il
parsing dell'sql e il calcolo del query plan: meno OCIPrepare e quindi
un design più complesso, sono giustificati nel caso di statement
riutilizzati moltissime volte mentre non lo sono affatto nel caso di
statement "usa e getta"
just my 2 cents
Marco

 approccio basato su Boost.Asio.
Purtroppo il progetto e' "ibernato" per mancanza di tempo libero.
Pero' se trovo qualcuno disposto a pagarmi per andare avanti... ;-)
Di Boost.Asio so poco, salvo il fatto che il suo autore e' uno che ne
capisce. Di certo l'impianto logico che sta alla base della libreria
ha avuto un notevole successo a cominciare dalla sua incarnazione
originale, ACE, e senza dimenticare la sua variante Pitonica, Twisted.
Tra l'altro sono convinto che il multithreading sia destinato a
tramontare abbastanza rapidamente e penso che sia importante che le
API/framework concepiti oggi supportino anche un approccio di tipo
asincrono, basato su callback e/o su "future".
Ciao,
Nicola

 asincrono, basato su callback e/o su "future".
mi sembra molto interessante
potresti spiegarti meglio?
Ciao,
Marco

 Nicola
Saluti  Manlio Perillo

 il multithreading non è così male, purchè non sia preemptive.
Il multithreading e' male perche il modello su cui si fonda e'
definitivamente sorpassato. Nasceva infatti dall'osservazione che la
protezione offerta dalla gestione della concorrenza tramite processi
ha un prezzo che in determinati contesti risultava troppo caro. Allora
si e' scelto di rinunciare a tale protezione per implementare
attivita' parallele che avrebbero dovuto condividere molto del proprio
spazio di indirizzamento, introducendo meccanismi di protezione che
non richiedessero context switch, in alternativa a quelli di
comunicazione tra processi.
Il problema e' che adesso il modello delle CPU moderne corrisponde
molto da vicino a quello teorico su cui si fonda il concetto originale
di concorrenza virtuale. Con i sistemi multiprocessore i meccanismi di
sincronizzazione tra thread servono piu' per scatenare comunicazione
tra spazi di indirizzamento separati che per impedire l'accesso
contemporaneo a specifiche locazioni di memoria. In piu' si sta
andando rapidamente verso la possibilita' di garantire ad ogni
processo attivo di eseguire sempre e soltanto sullo stesso processore:
niente piu' context switch.
Non dico che si torni a pipe/fork/exec, pero' forse non faremmo male a
studiarci Erlang tutti quanti.
Ciao,
Nicola

 Nicola
Ciao

progettuali che le OCI permettono
Esatto. Infatti un punto fondamentale è proprio questo. Io lo faccio
anche per acquisire una maggiore dimestichezza col C++. Vorrei
buttarmi in un progetto "corposo" e che mi consenta di applicare ciò
che leggo dai vari Alexandrescu, Meyers, Sutter, et al ;-)

 studiarci Erlang tutti quanti.
Visto che non conosco Erlang e non ho tempo infinito a mia 
disposizione, mi dici perché dovrei studiarlo?
-- 
Articolo 33 della Costituzione italiana: L'arte e la scienza sono 
libere e libero ne è l'insegnamento

 disposizione, mi dici perché dovrei studiarlo?
Perche' e' un linguaggio pensato per la realizzazione di sistemi ad
alto parallelismo.
Ciao,
Nicola

 alto parallelismo.
Quando vedrò un linguaggio che per permetterà di scrivere un sort di un 
set di dati usando tutti i processori disponibili in quel momento mi 
interesserò della cosa. Non mi pare che Erlang faccia questo, ma ci ho 
dedicato 5 secondi scarsi. In questo momento cerco di usare il tempo 
tra un prodotto e un progetto per imparare quante più cose per 
integrare python in un programma c++.
Ciao, Davide
