lunedì 28 settembre 2015

Come linkare il client C di PostgreSQL da un programma C++ usando CMake

Mi sono imbattuto di recente sul problema di linkare una libreria condivisa con il driver di PostgreSQL, il tutto usando Linux su una macchina a 64 bit.

Il programma è in C++ quindi la prima scelta è ricaduta su libpqxx.
Con grande delusione ho notato che libpqxx da problemi se il programma principale segue lo standard C++ 11 o l'ultimo C++ 14.
Poco male, ho pensato di linkare direttamente il client C di PostgreSQL, tuttavia la documentazione in giro non è molta.
Sono riuscito nell'impresa con questi accorgimenti:

nel file CMakeList.txt bisogna mettere
link_directories (/usr/lib64)
target_link_libraries (nomedelprogetto pq)

pq indica il file libpq.so, che si trova in /usr/lib64.

Nella parte C++ bisogna che l'inclusione della parte del driver indichi che si tratta di una libreria in C, quindi:

extern "C" {
    include <libpq-fe.h>
}

Questo permette di usare strutture e funzioni del driver, come PQconnectdb, PQexec, PGconn, PGresult, PQgetvalue, PQclear o PQfinish ecc...


venerdì 11 settembre 2015

Riferimenti Incrociati

Una lezione che non mi aspettavo, durante questo mio studio del C++ è che i riferimenti incrociati sono un anti pattern, una pratica da evitare.
Il C++ onestamente gestisce malissimo i riferimenti incrociati.
Con riferimento incrociato si intente una classe A che ha un riferimento a una classe B, mentre proprio quest'ultima ha un riferimento alla classe A.

Esiste un trucco che si chiama class forwarding che consiste nel dichiarare la classe chiamata in una delle due, però non si possono chiamare i metodi degli oggetti di tale classe.

Diverso è il caso nel quale si ricorra all'uso dei vecchi puntatori puri, i quali però hanno il tragico difetto che non dicono chi è l'incaricato di liberare la memoria.

Questo succede perchè il compilatore, mentre lavora su A, non conosce la dimensione di B e, mentre esamina B, non ha ancora finito di compilare A.
Mentre in Java il problema non si presenta, perché il compilatore fa un lavoro più semplice, trattandosi di un linguaggio con molte meno regole e sfumature, è impossibile per un compilatore C++ lavorare su classi parzialmente compilate.

Funziona con i puntatori perché la dimensione del puntatore è fissa, indipendentemente dall'oggetto puntato.

Dopo il primo shock quando mi sono accorto di questa limitazione, ho fatto un po' di ricerche.

Effettivamente i riferimenti circolari sono una possibile fonte di problemi, si pensi per esempio ai loop infiniti. C'è chi li considera un esempio di cattiva progettazione. Personalmente considero questa affermazione come un po' forte, di sicuro con il C++ sono un problema.

In Java li ho sempre visti con grande libertà. Ripensare un programma con molti riferimenti circolari, di sicuro porta a una struttura finale decisamente più semplice.


mercoledì 9 settembre 2015

Puntatori intelligenti per problemi stupidi

Negli ultimi mesi ho studiato una parte delle novità introdotte negli ultimi anni nella libreria standard di C++.

I puntatori puri, le famose stelline * tanto famose con il C sono di fatto quasi deprecati, alcuni autori dicono che non ci sono ragioni oggi per usarli se non quando si presentino problemi di prestazioni o in caso di risorse molto ridotte.

Devo dire che districarmi tra puntatori, riferimenti, shared_ptr e unique_ptr per me abituato a Java, è stato piuttosto difficile: la gestione della memoria "semi automatica" del nuovo C++ e la scelta tra tenere un'istanza nello stack o nello heap impongono quasi di imparare a programmare di nuovo.

Attualmente il mio livello di comprensione mi porta alle seguenti conclusioni.

Effettivamente il puntatore puro è evitabile in un normale programma gestionale, a meno che non ci si debba interfacciare con delle librerie che lo richiedono.


In C++ oggi è importante stabilire quale oggetto è proprietario di un altro e il proprietario è quello che determina il rilascio del secondo oggetto, cioé quando il proprietario è distrutto, questo determina la distruzione del secondo oggetto. Tale operazione si può fare in automatico, usando un unique_ptr.

Nel caso di variabili di istanza, io ho individuato i seguenti casi:

Il riferimento indicato con la e commerciale & è comodissimo per fare riferimento all'istanza di un altro oggetto se sicuramente mai nullo e mai usato dopo il suo rilascio, senza che il primo ne sia il proprietario.

Una variabile di istanza di un oggetto di proprietà mai nullo, se non è troppo grande, può stare nello stack senza puntatori di alcun genere o riferimenti.

Un riferimento ad un oggetto di proprietà, ma che può valere null è un buon candidato per essere puntato da un unique_ptr o da uno shared_ptr, perché il riferimento & non può mai valere null.

Una variabile di istanza di un oggetto che è di proprietà di un altro oggetto, che potrebbe valere null, potrebbe essere un candidato per uno shared_ptr.

Una lista concatenata o un vettore di oggetti che sono proprietà di altri oggetti, dovrebbe contenere degli std::reference_wrapper.

Una lista concatenata o un vettore (si chiamano contenitori in C++, sarebbero le Collections di Java) di oggetti di proprietà,  può mantenere tali oggetti nello stack.

L'uso di shared_obj non poi così limitato come si legge in giro. Si usa dove si potrebbe usare & ma c'è il rischio che l'oggetto puntato sia tolto dalla memoria prima del riferimento e dove si cerchi di chiamare i metodi dell'oggetto tolto.

Questo unito all'impossibilità di valere null, è un vero e proprio rischio nell'usare il riferimento &. A differenza di Java, in C++ è possibile per errore togliere dalla memoria un oggetto che ha dei riferimenti attivi, così come è possibile tentare di cancellare gli oggetti più di una volta, creando problemi com'è ovvio...

In generale, a meno che non si tratti di oggetti enormi, conviene tenere tutto nello stack, perché è più semplice, il problmema è se il dato può valere null, personalmente non capisco perché si sia rimandata l'introduzione della classe Optional allo standard C++ 17, ma pazienza...

Dopo questi mesi di studio, mi sento di affermare che il C++ offre diverse possibilità di gestione della memoria manuale, che però richiede molta attenzione.