Libpqxx souci pour renvoiyer l'objet de la connection

Hello

on va faire simple :slightly_smiling:

/*Je voudrais renvoyer un objet qui contiens la connections ,seulement évidement quelque chose m'echappe 
Le main est vide , car c'est la declaration de la fonction initconnect qui coince :S
*/
main.cpp
#include <stdlib.h>	// Nécessaire pour exit(), EXIT_SUCCESS, et EXIT_FAILURE
#include <iostream> 	// requis pour cerr
#include <pqxx/pqxx>	// définitions pour libpqxx
#include <iostream>
#include <string>
#include <sstream>

using namespace pqxx;
using namespace std;
static const char conn_str[] = "user='user' password='post' host='127.0.0.1' dbname='dbase'";

bool createTable(pqxx::work & txn) {
  try 
  {
		txn.exec("CREATE TABLE Employee (name text,department INTEGER )");
	}
	catch (...)
	{
		
		return true;
	}
		return false;
}
void deleteTable(pqxx::work & txn){
	txn.exec("DROP TABLE Employee");
}
void commit(pqxx::work & txn){
	txn.commit();
}

connection initconnect()// comment on déclare ce type ? 
{
    connection    myConnection(conn_str);
    myConnection.activate();
    if (myConnection.is_open())
    {
		return myConnection;
	}
}
int main()
{}


/*erreur du compilateur*/
make && ./main
c++ -lpq -lpqxx -Wall -Wextra -pedantic -D_FILE_OFFSET_BITS=64   -c -o main.o main.cpp
In file included from /usr/include/pqxx/basic_connection:19,
                 from /usr/include/pqxx/connection.hxx:26,
                 from /usr/include/pqxx/connection:19,
                 from /usr/include/pqxx/pqxx:18,
                 from main.cpp:3:
/usr/include/pqxx/connection_base.hxx: In copy constructor ‘pqxx::basic_connection<pqxx::connect_direct>::basic_connection(const pqxx::basic_connection<pqxx::connect_direct>&)’:
/usr/include/pqxx/connection_base.hxx:904: erreur: ‘pqxx::connection_base::connection_base(const pqxx::connection_base&)’ is private
/usr/include/pqxx/basic_connection.hxx:50: erreur: à l'intérieur du contexte
main.cpp: In function ‘pqxx::connection initconnect()’:
main.cpp:37: note: synthesized method ‘pqxx::basic_connection<pqxx::connect_direct>::basic_connection(const pqxx::basic_connection<pqxx::connect_direct>&)’ first required here 
make: *** [main.o] Erreur 1

/*
documentation si sa peux aider:
http://pqxx.org/devprojects/libpqxx/doc/stable/html/Reference/
*/

je pourrait faire cela

mai la variable est local, hors je ne voit pas comment faire autrement
une idée ? car en attendant je vai passer par une variable global ,dasn une classe sa deviendra une variable membre privée ,mai bon …

[quote=“panthere”]connection initconnect()// comment on déclare ce type ? { connection myConnection(conn_str); myConnection.activate(); if (myConnection.is_open()) { return myConnection; } }[/quote]
Deux gros problèmes avec ta fonction :

  • ça tu t’en étais déjà aperçu, la classe “connection” (enfin, “connection_base” plus exactement) n’a pas de constructeur de copie
  • si ta connexion est ouverte tu retournes l’objet, mais sinon ???

Autre problème pour moi : la doc que tu cites ne contient aucune info sur une classe “pqxx::connection” (il y a “connection_base” et le template “basic_connection” mais c’est tout). Je vais donc me baser sur le prototype de ces deux classes.

Visiblement tu es obligé d’établir la connexion lors de la construction de l’objet, pas d’autre choix possible. Ça exclut de passer un objet “connection” vide en référence à ta fonction qui l’initialiserait alors.

Donc si tu tiens absolument à ce que ça soit ta fonction qui “crée” l’objet, t’as pas vraiment le choix : faut l’allouer sur le tas (new). Et comme d’habitude en C++, pense bien à mettre ça dans des smart pointers pour éviter les fuites de mémoire en cas d’exception (apparemment le constructeur et la méthode activate() peuvent throw).
Bref, le proto de ta fonction va ressembler à :

Note : c’est mieux de passer la chaîne de connexion en paramètre de ta fonction, et de faire la liaison avec ta variable statique au moment de l’appel de fonction. Sinon tu vas faire comment quand il va falloir récupérer ces infos à partir d’un fichier de config ? :wink:

Merci pour tes explication
Quand j’aurais fait la classe je passerai par une variable membre,privée , de cette manière le fonction main pourra charger le fichier de config.je passerai en paramettre a la fonction secondaire les parramettre et ouvrire la connection, qui sera stoquée dans la variable privée c’est pas géniale mai bon sa va éviter de joué avec le tas.
quand pense tu ?

J’en pense que ça ne fera pas grande différence :

  • tu vas pas créer/supprimer sans arrêt des milliers d’objets de ce type donc ça n’aura aucune influence sur la consommation mémoire, la fragmentation, ou les performances de l’allocation
  • un smart pointer te rajoute une indirection mais ici ça n’a aucune incidence sur les performances (un simple accès DB est des millions voire milliards de fois plus coûteux)
  • l’un et l’autre sont tout aussi pratiques et sûrs à utiliser (faut pas faire l’erreur de mettre un pointeur nu dans ta classe, mais un smart pointer y’a aucun souci à se faire)

La seule vraie différence que je voie dans ton cas, c’est que si tu mets ton objet “connection” directement dans ta classe alors tu es obligé d’initialiser la connexion dans la liste d’initialisation du constructeur de ta classe, alors que si tu passes par un smart pointer tu peux créer l’objet plus tard dans le corps du constructeur. Ça influe juste sur les catch que tu peux faire et sur la propreté du code, ça dépend de ce que tu veux faire précisément.

Par exemple :

  • dans la liste d’initialisation tu ne peux absolument pas absorber l’exception en silence via un bloc try de fonction, ça serait UB : objet pas fini d’être construit et qui pourtant ne throw pas.
  • si tu veux transformer les exceptions de la connexion (catch un truc / rethrow autre chose) alors je trouve que c’est plus propre avec un smart pointer, ça évite justement un bloc try de fonction et permet de faire un bloc try plus localisé, uniquement pour la création de la connexion.

Bref, je pense que la solution smart pointer est très légèrement plus souple à utiliser, et le code sera plus propre si tu veux catch les exceptions dans ton constructeur ; comme les deux premiers points que j’ai cité (nombre d’instances / indirection) ne sont pas un problème, tu as donc le choix. Mais ce sont quand même de tous petits détails. :wink:

Merci pour ta réponse bon finalment je vais opter pour le pointer inteligent (smart pointer) :023

bon alors voila ce que sa donne j’ai pas utiliser le smart pointer finalement ,car je ne maitrise pas le sujet, Bref voila un bug
bon sa compile:

la première fois la table est cree, mai les donnée ne son pas ajoutée pas de message d’erreur… :017
la deusieme fois. Cela miaule aux moment de l’insertion des donnés … :108

Je ne comprend pas pourquoi ça n’insert pas les données ???

#include <stdlib.h>	// Nécessaire pour exit(), EXIT_SUCCESS, et EXIT_FAILURE
#include <iostream> 	// requis pour cerr
#include <pqxx/pqxx>	// définitions pour libpqxx
#include <iostream>
#include <string>
#include <sstream>

using namespace pqxx;
using namespace std;
const char conn_str[] = "user='tonuserz' password='tonpass' host='127.0.0.1' dbname='ma_base'";
connection *p_connect=NULL;
pqxx::work *p_work=NULL;

bool createTable() {
	std::string name="name,";
	std::string  T="Employee";

  try 
  {
		(*p_work).exec("CREATE TABLE  Employee(name TEXT,secteur INTEGER)");
	}
	catch (...)
	{
		
		return true;
	}
		return false;
}

void AddData() {
  (*p_work).exec("INSERT INTO Employee(name,secteur) VALUES ('mavaleur','1')");
}
void deleteTable(){
	(*p_work).exec("DROP TABLE Employee");
}
void commit(){
	(*p_work).commit();
}
void closeconnect(){
	(*p_connect).deactivate();
}
void initconnect()
{
	  try 
	{
		p_connect=new connection(conn_str);
		p_work = new pqxx::work (*p_connect);
	}
	  catch( runtime_error & e )
	  {
		cerr << "Echec à la connexion avec erreur := " << e.what();
		exit( EXIT_FAILURE );
	  }
	  catch( exception & e )
	  {
		cerr << e.what();
		exit( EXIT_FAILURE );
	  }
	  catch( ... )
	  {
		cerr << "Erreur/exception inconnue !" << endl;
		exit( EXIT_FAILURE );
	  }
}

int main()
{
	std::cout<< "ouverture db"<< std::endl;
	initconnect();
	std::cout<< "Cree table"<< std::endl;
	createTable();
	std::cout<< "ajout de donnée"<< std::endl;
	AddData() ;
	//std::cout<< "Supprimer table"<< std::endl;
	//deleteTable();
	std::cout<< "Commit"<< std::endl;
	commit();
	std::cout<< "fermeture db"<< std::endl;
	closeconnect();
	std::cout<< "delete pointeur p_connect "<< std::endl;
	delete p_connect;
	std::cout<< "delete pointeur p_work "<< std::endl;
	delete p_work;
	}

un makefile si sa peux rendre service :slightly_smiling:

SRC=$(wildcard *.cpp)
#SRC=main.cpp
OBJ=${SRC:.cpp=.o}
OUT=main

CXX=c++
CXXFLAGS=-lpq -lpqxx -Wall -Wextra -pedantic -D_FILE_OFFSET_BITS=64
#CXXFLAGS=-lsqlite3 -Wall -Wextra -pedantic -static


all: $(OUT)

$(OUT): $(OBJ)
	$(CXX) $^ -o $@ $(CXXFLAGS)

.cpp.o:

exe: all
	./$(OUT) "sqlite3.sql"

clean:
	@rm -v *.o
	@rm -v ./main
	true

mrproper: clean
	@rm $(OUT)

l’erreur

$ make && ./main
c++ -lpq -lpqxx -Wall -Wextra -pedantic -D_FILE_OFFSET_BITS=64   -c -o main.o main.cpp
c++ main.o -o main -lpq -lpqxx -Wall -Wextra -pedantic -D_FILE_OFFSET_BITS=64
ouverture db
Cree table
ajout de donnée
Commit
fermeture db
delete pointeur p_connect 
delete pointeur p_work 



$ make && ./main
make: Rien à faire pour « all ».
ouverture db
Cree table
ajout de donnée
terminate called after throwing an instance of 'pqxx::usage_error'
  what():  Error executing query .  Attempt to activate transaction<READ COMMITTED> which is already closed
Abandon

Une idée ?

J’ai lu ton code en diagonale, y’a rien d’immédiatement visible mais j’ai pas trop le temps de m’y plonger.

Par contre, juste une petite remarque. :wink:

C’est bien dommage, car ça repose sur un concept de base du C++ : le RAII (Resource Acquisistion Is Initialisation), qui est lui-même intimement lié à un autre concept de base : les exceptions et leur gestion correcte (Exception Safety). Si tu ne connais pas correctement ces deux points (je ne dis pas les maîtriser, ça prend des années :confused:) tu n’écris en fait que du « C avec des classes », mais comme tu utilises aussi des libs C++ qui, elles, intègrent bien ces notions, ça ouvre la voie à plein de bugs subtils.
Bref, je te conseille vivement de te documenter à fond là dessus, c’est vraiment très important pour faire du C++ correct.

Un bon point de départ c’est les GOTW d’Herb Sutter (en anglais) qui traitent beaucoup de ces sujets, de manière aussi abordable que possible.

  1. bien comprendre le principe d’Exception Safety et ses diverses déclinaisons
  2. après, le RAII est assez simple à comprendre
    Les smart pointers ne sont qu’un cas particulier de RAII, pas besoin de te documenter particulièrement dessus, quand tu auras compris (2) tu auras forcément compris les smart pointers aussi. Et tu verras le C++ d’une toute autre manière. :wink:

Exemple typique que je donne souvent (d’ailleurs en entretien ça permet de faire le tri rapidement :mrgreen:)… En l’état ce code a potentiellement un gros problème, lequel, pourquoi ? Comment le corriger (2 manières) ?

void fonction() { Classe* obj = new Classe(); obj->methode(); delete obj; }
:006

[quote=“syam”]J’ai lu ton code en diagonale, y’a rien d’immédiatement visible mais j’ai pas trop le temps de m’y plonger.

Par contre, juste une petite remarque. :wink:

C’est bien dommage, car ça repose sur un concept de base du C++ : le RAII (Resource Acquisistion Is Initialisation), qui est lui-même intimement lié à un autre concept de base : les exceptions et leur gestion correcte (Exception Safety). Si tu ne connais pas correctement ces deux points (je ne dis pas les maîtriser, ça prend des années :confused:) tu n’écris en fait que du « C avec des classes », mais comme tu utilises aussi des libs C++ qui, elles, intègrent bien ces notions, ça ouvre la voie à plein de bugs subtils.
Bref, je te conseille vivement de te documenter à fond là dessus, c’est vraiment très important pour faire du C++ correct.

Un bon point de départ c’est les GOTW d’Herb Sutter (en anglais) qui traitent beaucoup de ces sujets, de manière aussi abordable que possible.

  1. bien comprendre le principe d’Exception Safety et ses diverses déclinaisons
  2. après, le RAII est assez simple à comprendre
    Les smart pointers ne sont qu’un cas particulier de RAII, pas besoin de te documenter particulièrement dessus, quand tu auras compris (2) tu auras forcément compris les smart pointers aussi. Et tu verras le C++ d’une toute autre manière. :wink:

Exemple typique que je donne souvent (d’ailleurs en entretien ça permet de faire le tri rapidement :mrgreen:)… En l’état ce code a potentiellement un gros problème, lequel, pourquoi ? Comment le corriger (2 manières) ?

void fonction() { Classe* obj = new Classe(); obj->methode(); delete obj; }
:006[/quote]
Le comment c’est un peux vague mai en gros:
le risque c’est déjà que l’objet peut être trop gros pour la mémoire, ensuite le pointeur est pas nul après le delete, (un peux ce que j’ai fait mai bon…).
Ce qui peux si le pointeur est à nouveau delete, mettre le programme dans un état indéfini alors que si le pointeur est mis a nul , ce problème ne ce pose pas.

donc un simple obj=0; evite le probleme
ensuite avec les pointeur, le souci c’est que le debugage est rendu plus dificile
après je vois pas ? :017
Sinon je pense que je vai me documenter dessus. mai je vai m’en passer pour le moment :slightly_smiling:

Ça c’est tout aussi valable si tu construis l’objet sur la pile plutôt que sur le tas, c’est un risque qui existe quasiment à chaque ligne de code qu’on écrit donc on va dire que c’est pas ça. :mrgreen:

Rien à voir, on sort immédiatement de la fonction donc plus aucun moyen d’accéder au pointeur, aucun risque de double delete.


Bon je te mets sur la piste : il y a potentiellement une fuite de mémoire, le delete n’a aucune garantie d’être exécuté ! (ça couvre “lequel” et le début du “pourquoi”) :wink:

J’ai plus fait de C++ depuis longtemps, mais je pense que les solutions sont évidentes maintenant.
Solution moche (il faudrait probablement faire quelque chose dans le catch) :

void fonction() { Classe* obj = new Classe(); try { obj->methode(); } catch (...) { } delete obj; }

Solution plus propre :

void fonction() { std::auto_ptr<Classe> init_connect(new Classe()); obj->methode(); }

Il faut noter que les deux solutions ne sont pas équivalente. Dans la première les erreurs sont masquées (ce qui est mal en soit) et ne plus les masquer peut rendre le code complexe (dans le catch on libère puis on lance notre propre exception) en multipliant les points où l’on doit libérer la mémoire, alors que dans le second cas on peut « catcher » les exceptions et en relancer sans risquer de fuite mémoire.

Edit : Personnellement j’aime beaucoup le mot clef with de python et le try-with-resources de java pour faire ce genre de chose.

la deuxième est bonne :wink:
c’est bien LA solution recherchée.

la première… tu as suffisamment expliqué ses défauts, rien à rajouter

la deuxième solution que je mentionnais c’est tout bêtement instancier l’objet sur la pile

void fonction() { Classe obj; obj.methode(); }
juste histoire d’être exhaustif quoi… mais bien évidemment c’est pas le but de l’exercice, y’a qu’à voir comment j’ai insisté sur les exceptions et le raii dans ce fil pour s’en rendre compte :slightly_smiling:

@panthere : juste pour que ça soit clair pour toi… obj->methode PEUT throw (on n’a pas d’infos dessus donc on doit partir du principe que ça peut) :wink:

Malgrès le fait que j’apprécie beaucoup le c++, c’est un point qui est appréciable en Java :
[ul]
[li]tu es obligé d’indiquer les exceptions que tu lève dans une méthode ;[/li]
[li]tu es obligé de les prendre en compte quand tu utilise une méthode.[/li][/ul]

[quote=“MisterFreez”]c’est un point qui est appréciable en Java :
[ul]
[li]tu es obligé d’indiquer les exceptions que tu lève dans une méthode ;[/li]
[li]tu es obligé de les prendre en compte quand tu utilise une méthode.[/li][/ul][/quote]
et c’est aussi une des grosses critiques que tout le monde fait à l’encontre de java :wink:
les checked exceptions sont une plaie… soit tu les masques d’une manière ou d’une autre (le moins pire étant de les transformer en exceptions personnalisées plus génériques) soit au bout de 4-5 niveaux d’appels tu te retrouves avec une liste longue comme le bras :frowning:
(note: je suis un noob en java, il y a peut-être des techniques que je ne connais pas pour éviter ces deux désagréments)

perso je préfère encore la méthode c++ : prévoir le pire

[quote=“syam”][quote=“MisterFreez”]c’est un point qui est appréciable en Java :
[ul]
[li]tu es obligé d’indiquer les exceptions que tu lève dans une méthode ;[/li]
[li]tu es obligé de les prendre en compte quand tu utilise une méthode.[/li][/ul][/quote]
et c’est aussi une des grosses critiques que tout le monde fait à l’encontre de java :wink:[/quote]
Faut réfléchir à ses exceptions c’est surtout ça qui n’est pas maîtrisé ou souvent mis de coté.

Dans un code, tu as deux options. Soit c’est à ton niveau de gérer l’erreur (pour faire du rejeu, envoyer un message d’erreur ou autre), soit ce n’est pas à toi et tu la transmet à l’appelant.

Je ne comprends pas de quel liste tu parle ? Si tu parle des classes que tu met à la suite du throws c’est une question de conception.
À quoi est ce que s’intéresse le code appelant et/ou la méthode. La méthode doit regrouper les exceptions pour simplifier la gestion par le code appelant.

Il faut réfléchir à quels informations ont veut fournir au code appelant. Ainsi on les regroupe en les encapsulant dans d’autres exception dont la sémantique est plus simple pour l’utilisateur de la méthode (sans perte d’informations !).

Je travail sur un projet en Java assez gros (je ne sais pas combien de ligne de code, mais on doit être à 10 ou 20 milles) et je n’ai pas rencontré de throws avec plus de 3 classes.

Ça permet de faire des erreurs ou ça oblige à sur blinder du code pour rien (ou à utiliser un outil qui feras la même chose que ce que fait Java).

C’est un peu comme les langages type OCaml qui ne gère pas de valeur null, il lui préfère un type null. Du coup quand tu récupère une variable soit tu es certains (par inférence de type) qu’il ne peut pas être null, soit tu es obligé par le compilateur à gérer la valeur null (par pattern matching). C’est contraignant c’est sur, mais ça améliore la fiabilité du code.