OVH Cloud OVH Cloud

interfacer la lecture sous divers formats

6 réponses
Avatar
meow
Bonjour,

Je souhaiterais d=E9crire une m=E9thode de lecture sur un flux pour
remplir une structure de points... Alors ce =E0 quoi j'ai pens=E9 :

une classe abstraite 'feeder' pour d=E9finir une m=E9thode virtuelle pure
getNextPoint()
des classes d=E9riv=E9es 'feeder_XXX' impl=E9mentant getNextPoint()
suivant le format

class Feeder {
public:
typedef enum { ok=3D0,ko,eof /*end of feed*/} Status;
Feeder();
virtual ~Feeder();

virtual Status getNextPoint(Quad<T> &q)=3D0;
}
/* (o=F9 Quad<T> est le pendant de std::pair pour les quadruplets)*/

class FeederXYZW : public Feeder {
protected:
std::ifstream fin;
public:
FeederXYZW(char const * const filename):fin(filename){}
virtual ~FeederXYZW();
virtual typename PointFeeder<T>::Status getNextPoint(Quad<T> &q){
double x,y,z;
double w;
if (fin.eof()) return eof;
if ((fin>>x)&&(fin>>y)&&(fin>>z)&&(fin>>w)) {
// TODO : here I should check and assert
p[0]=3Dx;p[1]=3Dy;p[2]=3Dz;p[3]=3Dw;
return ok;
}
else {
return ko;
}
}
};
}

la m=E9thode vous parrait-elle correcte ?

6 réponses

Avatar
meow
oups, je me rends compte que dans mon exemple précédent j'ai laissé
quelques bourdes, en particulier, à la place de
virtual typename PointFeeder<T>::Status getNextPoint(Quad<T> &q){
il faut lire
virtual typename Feeder::Status getNextPoint(Quad<T> &q){
et avoir la bonté de passer outre l'utilisation intempestive de T qui
sous entend un template...

Sinon, sur le meme sujet J'ai un autre problème : j'aimerai pouvoir
rendre ma lecture de flux indépendante du type de flux : un istream
fourni par l'utilisateur ou bien un fichier à chercher et ouvrir si
l'utilisateur me donne son nom
bref, quelque chose dans l'idée de :

class FeederXYZW : public Feeder {
protected:
std::istream is;
public
FeederXYZW(char const * const
filename):is((istream)ifstream(filename)){}
FeederXYZW(istream &i):is(i){}
...
};

bien entendu aucun de ces deuc constructeurs ne fonctionne mais je n'ai
aucune idée des raisons pour lesquelles le compilo m'injurie... Une
histoire de copy constructor private dans ios_base
...Un workaround à me proposer ?
Avatar
kanze
meow wrote:

Je souhaiterais décrire une méthode de lecture sur un flux
pour remplir une structure de points... Alors ce à quoi
j'ai pensé :

une classe abstraite 'feeder' pour définir une méthode
virtuelle pure getNextPoint()


Je ne sais pas si la classe est si nécessaire ici. Si les points
à lire sont toujours en format text, un surcharge de l'opérateur
est la solution classique :



std::istream&
operator>>( std::istream& source, Point& dest ) ;

Je commencerais par là, de toute façon, même si l'encapsulation
dans une classe Feeder se justifie par ailleurs (par exemple,
parce que les formats ne sont pas toujours text, ou parce que le
fichier contient d'autres données, comme le nombre de points en
tête).

des classes dérivées 'feeder_XXX' implémentant getNextPoint()
suivant le format

class Feeder {
public:
typedef enum { ok=0,ko,eof /*end of feed*/} Status;
Feeder();
virtual ~Feeder();

virtual Status getNextPoint(Quad<T> &q)=0;


C'est quoi, T, ici ?

Ou bien, Feeder doit être un template (avec paramètre T), ou
bien, il faut préciser T.

}
/* (où Quad<T> est le pendant de std::pair pour les quadruplets)*/

class FeederXYZW : public Feeder {
protected:
std::ifstream fin;


Pourquoi protected ?

Et en passant, je sais que c'est un exemple, mais FileFeeder
serait un meilleur nom, non ?

public:
FeederXYZW(char const * const filename):fin(filename){}
virtual ~FeederXYZW();
virtual typename PointFeeder<T>::Status getNextPoint(Quad<T> &q){
double x,y,z;
double w;
if (fin.eof()) return eof;
if ((fin>>x)&&(fin>>y)&&(fin>>z)&&(fin>>w)) {
// TODO : here I should check and assert


Pourquoi assert ?

En fait, il te faut plus de conditions de rétour que tu en as :
ok, échec à l'ouverture, fin de fichier, erreur de formattage,
erreur « dure » (erreur de lecture disque, etc.)...

Note aussi que le test de eof() avant ne garantit pas que tu
n'es pas à la fin de fichier. Son seul intérêt, c'est d'éviter
les operator>> si on a déjà vu la fin de fichier.

En gros, *après* une extraction (opérateur >>) :

-- si le flux est bon (flux s'évalue à vrai dans un
conditionnel), la lecture s'est bien passée, et les valeurs
sont lue,

-- si le flux évalue faux, on a plusieurs possibilités :

o flux.bad() est vrai : il y a eu une erreur dure
(problème hardware, etc. -- en fait, le flux même a levé
une exception).

o sinon, si ! flux.eof() on a eu une erreur de format dans
les entrées, un caractère alphabétique où on s'attendait
à un nombre, par exemple,

o sinon, il y a de fortes chances qu'on soit à la fin de
fichier (mais il y a des erreurs de format qui
positionne eof aussi, on ne peut donc pas être 100%
sûr).

Avant l'essai d'extraction, tout ce qu'on peut savoir, c'est que
si une condition signalée (eof, fail ou bad) existe,
l'extraction échouera. Si fail ou bad sont vrai, l'état du flux
ne changera pas. Si eof est vrai, et les autres deux faux, fail
deviendra vrai.

p[0]=x;p[1]=y;p[2]=z;p[3]=w;


C'est quoi, ce p ?

return ok;
}
else {
return ko;
}
}
};
}

la méthode vous parrait-elle correcte ?


L'idée en soi, je ne peux pas dire. Ça dépend de l'application.
Pour le cas concret de faire ce que tu as fait ci-dessus,
quelque chose comme :

template< typename T >
istream&
operator>>( istream& source, Quad< T >& dest )
{
T x, y, z, w ;
source >> x >> y >> z >> w ;
if ( source ) {
dest = Quad< T >( x, y, z, w ) ;
}
return source ;
}

Mais c'est limité. Tes données ont une structure. Est-ce que
cette structure ne se reflette pas dans le format d'entrée ? Par
exemple, on impose un point par ligne :

template< typename T >
istream&
operator>>( istream& source, Quad< T >& dest )
{
std::string line ;
if ( std::getline( source, line ) ) {
std::istringstream s( line ) ;
T x, y, z, w ;
s >> x >> y >> z >> w ;
if ( s ) {
dest = Quad< T >( x, y, z, w ) ;
} else {
source.setstate( std::ios::failbit ) ;
}
}
return source ;
}

Si ensuite tu estîmes qu'il faut une classe Feeder, je n'en
ferais qu'une classe concrète pour les istream, qui prend un
istream& comme paramètre de construction (et donc, l'ouverture
de fichier se trouve en dehors de la classe), et qui utilise
l'opérateur ci-dessus dans son implémentation. D'autres classes
concrète s'occupera des entrées binaire, etc.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
meow
Je ne sais pas si la classe est si nécessaire ici. Si les points
à lire sont toujours en format text, un surcharge de l'opérateur
est la solution classique
Le nombre de types d'obtention est suceptible d'etre augmenté... J'ai



essayé de masquer le fond du problème pour focaliser l'attention sur
la part "technique", mais je crois que je n'échapperai pas à un
explication plus détaillée. Donc, en fait je suis toujours sur mon
problème de détection de trous dans les proteines.
La première étape consiste à lire les données relatives aux atomes
: coordonnées spatiales plus rayon atomique (ce que je charge dans mes
Quad<T>).
Ces données sont généralement stockées dans des fichiers;
généralement au format pdb. Bien entendu ce format est assez complexe
et contient un grand nombre de données qui ne m'intéressent pas
dirrectement. Pour pouvoir aller rapidement à l'essentiel j'ai donc
ecrit un petit script pour génerer des fichiers au format :
ligne = x y z rayon
que je lis pour faire mes calculs.
A terme cependant je serai certainement amené à utiliser des
bibliothèques de gestion de formats biologiques afin d'etre capable de
lire dirrectement les pdb, mmcif et compagnie. Je connais pas encore
ces bibliothèques, mais à priori je pense qu'elle permettent de
garder les données structurées, de faire des selections dessus
(ensemble de la proteine, sous partie, etc...), et d'en extraire les
données qui m'intérressent : x y z rayon des atomes de la selection.

A bien y réfléchir, rien ne m'empechera alors de redéfinir
l'opérateur >> d'une classe SelectionResult vers Quad<T>...
Cela dit, comment ferais-je si un jour j'ai aussi à traiter des
fichiers "rayon x y z" à la place de "x y z rayon" ?

Enfin, en replaçant dans le contexte : (tiens, j'avais déjà
commencé à parler de mes feeder dans ce thread :
http://groups.google.fr/group/fr.comp.lang.c++/browse_thread/thread/91ba367 843a86751
) j'ai une classe VoidHandler qui me sert à "stocker des vides", elle
aggrège un objet de type PocketDetector (pattern strategie) auquel
elle délègue la detection de ces poches... J'avais donc naturellement
pensé à lui aggreger un PointFeeder sur le meme pattern (strategy).
Mais maintenant, je ne suis plus si certain que ce soit une bonne
idée, après tout, les données peuvent etre pré-chargées puis
fournies au VoidHandler... Là, j'avoue que je commence à etre un peu
paumé ! En quoi le choix de ce pattern pour le chargement des données
est-il bon/mauvais ?

Bon, reprenons le fil de tes réponses
C'est quoi, T, ici ?
oui, désolé j'ai oublié de stripper tous les paramètres templates

que je voulais cacher pour simplifier mon exemple. T est le type des
données que je load. généralement double.

std::ifstream fin;
Pourquoi protected ?

hum hum... j'avoue que j'ai encore du mal à argumenter sur ce genre de

sujets. Bon, je saisis l'occasion de ta question pour ouvrir une
parenthèse sur le sujet : Alors pas public parce que encapsulation des
données, et euh... pas private pour le cas où je voudrais étendre ma
classe... Bon, OK, ça tient pas la route :)
Par contre, entre temps je me suis rendu compte que je devrais plutot
remonter cet attribut dans la classe supérieure :
Feeder { protected : istream s;
public : getNextPoint()=0;
... }
FeederXYZW : public Feeder { ...
getNextPoint();
}

et là j'aurais le choix entre laisser is en protected si je veux par
exemple laisser l'accès à s en lecture pour FeederXYZW, ce qui est
sale. Ou le mettre en private auquel cas il faudra que Feeder
implémente une méthode protected readData utilisée dans les
getNextPoint() de ses filles... Correct ?

Et en passant, je sais que c'est un exemple, mais FileFeeder
serait un meilleur nom, non ?
Le vrai nom c'est PointFeeder, encore une fois je voulais masquer le

plus possible de parasites... Raté. Promis je le ferai plus.

Pourquoi assert ?
Parce que je me suis laissé emporter... Il est vrai qu'il n'est pas

nécessaire de crasher toute l'appli sur un problème de lecture :)

pour les erreurs sur le flux : oui, j'ai tendance à vouloir aller trop
vite et à considérer certaines choses comme secondaires... Cela dit,
pour l'utilisateur du Feeder à la limite tout ce qu'il veut savoir
c'est si il faut continuer à lire les données, si c'est terminé, ou
bien s'il y a eu une erreur... Le type d'erreur importe peu, non ?



Avatar
kanze
meow wrote:

Je ne sais pas si la classe est si nécessaire ici. Si les
points à lire sont toujours en format text, un surcharge de
l'opérateur >> est la solution classique


Le nombre de types d'obtention est suceptible d'etre
augmenté... J'ai essayé de masquer le fond du problème pour
focaliser l'attention sur la part "technique", mais je crois
que je n'échapperai pas à un explication plus détaillée. Donc,
en fait je suis toujours sur mon problème de détection de
trous dans les proteines. La première étape consiste à lire
les données relatives aux atomes : coordonnées spatiales plus
rayon atomique (ce que je charge dans mes Quad<T>).


Pour commencer : je définirais une classe Atom, plutôt
qu'utiliser un type générique. Je définierais probablement même
une classe Coordinates. Ces classes peuvent être très simple.
Éventuellement simplement des struct, mais avec constructeurs.
Mais un atome, c'est un atome, ce n'est pas n'importe quelle
collection de quatre double. Le typage statique est ton ami.

Ces données sont généralement stockées dans des fichiers;
généralement au format pdb.


J'y ai jeté un coup d'oeil. C'est un format conçu avec Fortran à
l'esprit. Il va poser certains problèmes en C++ (que
j'expliquerai plus loin).

Mais je comprends maintenant le but de ta classe (et
interface) : je crois en effet que c'est la bonne approche. (Je
l'aurais appeler ProteinDataSource, mais ProteinDataFeeder va
bien aussi. Ensuite, tu auras un PDBProteinDataFeeder, etc.)

Aussi, je lis (à
http://www.rcsb.org/pdb/static.do?p=file_formats/pdb/index.html)
que « This representation [...] and a large amount of software
using it has been written. » Ma première réaction alors serais
de voir s'il existe déjà une bibliothèque disponible que je
pourrais utiliser ; ce n'est pas la peine de réinventer la roue.

Bien entendu ce format est assez complexe et contient un grand
nombre de données qui ne m'intéressent pas directement. Pour
pouvoir aller rapidement à l'essentiel j'ai donc ecrit un
petit script pour génerer des fichiers au format :

ligne = x y z rayon
que je lis pour faire mes calculs.

A terme cependant je serai certainement amené à utiliser des
bibliothèques de gestion de formats biologiques afin d'etre
capable de lire dirrectement les pdb, mmcif et compagnie. Je
connais pas encore ces bibliothèques, mais à priori je pense
qu'elle permettent de garder les données structurées, de faire
des selections dessus (ensemble de la proteine, sous partie,
etc...), et d'en extraire les données qui m'intérressent : x y
z rayon des atomes de la selection.


C'est effectivement le cas des PDB, je crois, d'après le peu que
je viens de voir.

A bien y réfléchir, rien ne m'empechera alors de redéfinir
l'opérateur >> d'une classe SelectionResult vers Quad<T>...
Cela dit, comment ferais-je si un jour j'ai aussi à traiter
des fichiers "rayon x y z" à la place de "x y z rayon" ?


Des manipulateurs.

Mais les fichiers en question sont structurés. Conceptuellement,
l'opérateur >> parse du texte d'un flux de données (représenté
par un std::streambuf). Si tu as des données structurées, et non
un flux, il ne convient peut-être pas.

Dans le cas de PDB, il ne convient certainement pas. D'abord,
c'est un format structuré à base de lignes, avec chaque
enrégistrement dans une (ou dans certains cas, plusieurs) ligne.
Par défaut, les flux iostream traite un séparateur de ligne
comme un espace quelconque. Pour des formats simple, on
contourne habituellement ce problème en lisant avec
std::getline, puis en mettant la ligne dans un
std::istringstream pour le parser :

std::string line ;
while ( std::getline( source, line ) ) {
std::istringstream in( line ) ;
in >> ... ;
}

C'est un idiome très répandu pour lire des fichiers simples
orientés ligne, du genre fichiers de configuration. Et en
passant, je crois qu'une implementation de PDBProteinDataFeeder
se servira bien de std::getline pour la lecture physique.

Ensuite, le problème se corse. Si je régarde le format d'un
« atom », je constate que les champs sont définis selon le
numéro de colonne, et non par des séparateurs. C-à-d que les x,
y et z de l'atome se trouvent aux colonnes 31-38, 39-46 et
47-54, respectivement, et qu'il peuvent donc apparaître sans
séparateur, disons 1234.5678901.2345678.901 dans les colonnes
31-54. Et ça, pour des raisons que je ne connais pas, istream ne
sait pas traiter. (Il existe bien un champ pour préciser la
largueur maximum, mais pour des raisons historiques, il n'est
pas pris en compte par les entrées.)

J'ai un problème presqu'identique dans mon application actuelle.
ce que j'ai fait, c'est de définir une struct pour la structure
de la ligne, avec tous les membres de la struct des char[] ;
pour les atomes, ça donnerait quelque chose comme :

struct PDBAtomFmt
{
char recordName[ 6 ] ;
char serial[ 5 ] ;
char name[ 4 ] ;
// ...
char x[ 8 ] ;
char y[ 8 ] ;
char z[ 8 ] ;
char w[ 6 ] ;
// ...
} ;

Pour lire une ligne, quelque chose comme :

std::string line ;
std::getline( source, line ) ;
if ( ! source ) {
// Erreur de lecture...
}
if ( line.size() != sizeof( PDBAtom ) ) {
// Erreur de format...
}
PDBAtomFmt const& atom(
*reinterpret_cast< PDBAtomFmt const* >( line.data() ) ) ;
// ...
double x = getFloat( atom.x, sizeof( atom.x ), 3 ) ;
double y = getFloat( atom.y, sizeof( atom.y ), 3 ) ;
double z = getFloat( atom.z, sizeof( atom.z ), 3 ) ;
double w = getFloat( atom.w, sizeof( atom.w ), 2 ) ;
return Atom( x, y, z, w ) ;

Avec :

double
getFloat( char const* text, int length, int prec )
{
std::istrstream s( text, length ) ;
double result ;
s >> result ;
if ( ! s || s.peek() != EOF ) {
// Erreur de format...
}
return result ;
}

Si on veut d'avantage de vérification du format, on pourrait
ajouter quelque chose comme :

std::ostringstream reText ;
reText << " *[0-9]+.[0-9]{" << prec << "}" ;
boost::regex const re( reText.str() ) ;
if ( ! boost::match( text, text + length, re ) ) {
// Erreur de format...
}

au début de getFloat. (On pourrait aussi valider la ligne
entière au moyen d'une expression regulière au début de la
fonction getAtom. C'est probablement la solution que
j'adopterais, mais l'expression alors pour les réels de format
n.m n'est pas triviale. Quelque chose du genre
"( {2}[0-9]| [0-9]{2}|[0-9]{3}).[0-9]{3}" pour un 6.2, par
exemple.)

Plusieurs commentaires s'impose :

-- Ceci n'est pas du tout garanti par la norme du langage, qui
permet des octets de rembourage n'importe où dans
PDBAtomFmt. Il y a donc une certaine risque. Je m'en suis
servi, et je peux dire que ça marche avec g++, Sun CC et
VC++. Mais j'ajouterais quand même une validation statique
quelque part, du genre :

struct D { char c1[ 17 ] ; char c2[ 11 ] ; char c3[ 1 ] ;} ;
char errorIfUnwantedPadding[ sizeof( D ) == 29 ] ;

(C'est un hack affreux, mais parfois utile. S'il y a des
octets de rembourage, la taille ne serait pas 29.
L'expression de la dimension du tableau serait donc faux, ce
qui vaut 0. Et c'est une erreur de définir un tableau local
de taille 0.)

-- Officiellement, la classe std::istrstream est depréciée, et
g++ génère un avertissement. Ne t'inquiète pas. (Dans mon
cas, j'ai écris mon propre imemstream. Mais chaque chose en
son temps ; tu as encore des choses à apprendre avant
d'arriver à écrire tes propres flux, encore si c'est
beaucoup plus facile qu'on pourrait crois.)

Enfin, en replaçant dans le contexte : (tiens, j'avais déjà
commencé à parler de mes feeder dans ce thread :
http://groups.google.fr/group/fr.comp.lang.c++/browse_thread/thread/91ba3 67843a86751)
j'ai une classe VoidHandler qui me sert à "stocker des vides",
elle aggrège un objet de type PocketDetector (pattern
strategie) auquel elle délègue la detection de ces poches...
J'avais donc naturellement pensé à lui aggreger un PointFeeder
sur le meme pattern (strategy). Mais maintenant, je ne suis
plus si certain que ce soit une bonne idée, après tout, les
données peuvent etre pré-chargées puis fournies au
VoidHandler... Là, j'avoue que je commence à etre un peu paumé
! En quoi le choix de ce pattern pour le chargement des
données est-il bon/mauvais ?


Maintenant que j'ai compris la contexte, je crois que la
stratégie est bonne. Mais je crois que je garderais la lecture
et le traitement séparés quand même. Dans le C++ moderne, on
dirait de passer des itérateurs, dont le type est un paramètre
du template, à ton PocketDetector, mais je crois que
réalistiquement, lui passer une std::vector<Atom> const& serait
une meilleur solution.

En général, c'est une bonne idée d'écrire l'application comme
simplement de la colle qui joint des divers composants. Et c'est
prèsque toujours une bonne idée que les entrées/sorties se
trouve dans un autre composant que les traitements. Tu aurais
donc l'application qui s'adresse à un composant (dont le
comportement est polymorphique, et dépend du type du fichier
d'entrée) pour obtenir un vector<Atom>, ensuite à un autre
pour trouver les vides, et finalement à un troisième pour les
afficher.

Bon, reprenons le fil de tes réponses
C'est quoi, T, ici ?
oui, désolé j'ai oublié de stripper tous les paramètres

templates que je voulais cacher pour simplifier mon exemple. T
est le type des données que je load. généralement double.


Un conseil : fait marcher sans templates d'abord. (Sans
templates à toi, s'entend. Il ne faut pas se priver de
std::vector.) C'est déjà assez difficile comme ça.

std::ifstream fin;
Pourquoi protected ?



hum hum... j'avoue que j'ai encore du mal à argumenter sur ce
genre de sujets.


Je démande, parce que je crois que je n'ai jamais utilisé des
données protégées dans une classe visible aux autres. (Il
m'arrive de les utiliser dans des petites classes propre à
l'implémentation d'un composant.)

Bon, je saisis l'occasion de ta question pour ouvrir une
parenthèse sur le sujet : Alors pas public parce que
encapsulation des données, et euh... pas private pour le cas
où je voudrais étendre ma classe... Bon, OK, ça tient pas la
route :)


Pas publique, parce que ça brise effectivement l'encapsulation.
Et pas protégé, parce qu'en fin de compte, ce n'est pas
fondamentalement différent que publique -- n'importe qui qui
veut y accéder n'a qu'en dériver.

En fait, il y a des classes dont le rôle se limite vraiment à
agglomérer plusieurs données, pour empécher d'avoir une centaine
de paramètres dans une fonction, par exemple, où simplement que
ces données constituent une contexte commune à plusieurs
opérations. Dans ces classes-là, toutes les données sont
publiques.

Sinon, si la classe est visible à l'extérieur du composant,
toutes les données doivent être privées.

Par contre, entre temps je me suis rendu compte que je devrais
plutot remonter cet attribut dans la classe supérieure :
Feeder { protected : istream s;
public : getNextPoint()=0;
... }
FeederXYZW : public Feeder { ...
getNextPoint();
}


Éventuellement. Voire créer une classe intermédiaires :
StreamBasedFeeders.

En fait, je me démande si le cas du flux, ici, ne serait pas une
exception à la règle « pas de données protégées ». Dans la
mesure où la classe qui le fournit ne le fait que par commodité
pour les classes dérivées, et n'impose pas de règles en ce qui
concerne son utilisation. Mais j'avoue que ça me choque toujours
un peu. Dans de cas semblables, je l'ai toujours fait privé,
avec une fonction protégée istream& stream(). Mais si je donne à
la classe dérivée un moyen d'y obtenir une référence non const,
qu'est-ce que j'ai gagné par rapport à la déclarer protégée ?

et là j'aurais le choix entre laisser is en protected si je
veux par exemple laisser l'accès à s en lecture pour
FeederXYZW, ce qui est sale. Ou le mettre en private auquel
cas il faudra que Feeder implémente une méthode protected
readData utilisée dans les getNextPoint() de ses filles...
Correct ?


Voir ce que je viens d'écrire. Je me démande si ce n'est pas moi
qui applique trop rigueureusement une règle généralement
valable.

Et en passant, je sais que c'est un exemple, mais FileFeeder
serait un meilleur nom, non ?


Le vrai nom c'est PointFeeder, encore une fois je voulais
masquer le plus possible de parasites... Raté. Promis je le
ferai plus.

Pourquoi assert ?


Parce que je me suis laissé emporter... Il est vrai qu'il
n'est pas nécessaire de crasher toute l'appli sur un problème
de lecture :)


Ce n'est même pas souhaitable:-). Ce qu'il vaut mieux faire,
c'est :

1. Sortir un message d'erreur aussi précis que possible, avec
au minimum le nom du fichier et le numéro de la ligne où
l'erreur est apparue.

2. Si c'est une erreur de format, continue la lecture, afin de
détecter d'autres erreurs.

3. Mémoriser quand même le fait d'avoir eu une erreur, afin de
ne pas traiter les données par la suite.

Comme toujours, ce sont des règles générales. Elles tolèrent
certains exceptions. Dans ton cas, par exemple, j'imagine que
les fichiers en entrée sont plutôt écrits par d'autres
programmes, et non par une personne avec un éditeur. Dans ce
cas-là, le nom du fichier et le numéro de la ligne sont souvent
des informations suffisantes en ce qui concerne l'erreur. De
même, dans ce cas-là, ce n'est pas toujours nécessaire de
continuer -- si le programme qui écrit le fichier a une erreur,
et écrit les entrées des atomes avec un mauvais format, il y a
de fortes chances que toutes les lignes atome auront la même
erreur.

Aussi, dans certains cas (surtout quand on traite des
statistiques), il peut être préférable de simplement sauter
l'entrée erronée, et faire le traitement quand meme, sans le
prendre en compte. (Mais j'imagine qu'un atome qui manque
fausserait pas mal ton calcul de trou.)

pour les erreurs sur le flux : oui, j'ai tendance à vouloir
aller trop vite et à considérer certaines choses comme
secondaires... Cela dit, pour l'utilisateur du Feeder à la
limite tout ce qu'il veut savoir c'est si il faut continuer à
lire les données, si c'est terminé, ou bien s'il y a eu une
erreur... Le type d'erreur importe peu, non ?


Ça dépend. Si ton programme trouve une erreur de format dans un
fichier PDB, ça serait pas mal de savoir le numéro de la ligne,
non ? Ça doit aider considérablement à la récherche de l'erreur
dans le programme qui a écrit le fichier.

Même dans les istream, qui sont assez primitif à cet égard, on
distingue entre une erreur de format, et une erreur de lecture
physique. Pas assez, à mon avis, mais le principe y est.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
meow
Pour commencer : je définirais une classe Atom, plutôt
[..]

Mais un atome, c'est un atome, ce n'est pas n'importe quelle
Certes. J'ai toujours tendance à vouloir faire vite pour aller à ce

que je juge essentiel : mon algorithme et ses résultats. Bref, je
sous-estime l'importance de certains à coté et mon code finit par
devenir illisible.
hop, je me flagelles un coup et j'ecris ces classes.

Le typage statique est ton ami.
Euh... C'est quoi le typage statique (shame & blush)


ce n'est pas la peine de réinventer la roue.
Oui, c'est exactement ce que je disais : Je n'ai pas l'intention de

lire dirrectement ces fichiers pdbs. Comme dit, pour l'instant les pdbs
sont transformés en "x y z r" par un script. Et pour l'instant je ne
m'autorises que la lecture de ce type de flux; mais plus tard Je vais
certainement utiliser une bibliothèque de lecture des pdbs qui va
gentiment me donner une arborescence d'objets qui se rempliront tous
seuls avec les données d'un fichier pdb... ne me restera plus qu'à
ecrire un "pdbFeeder" qui ira chercher l'info utile (x y z r) dans ces
objets et me la fournira via une fonction getNextPoint()...

des fichiers "rayon x y z" à la place de "x y z rayon" ?
Des manipulateurs.

Qu'est- ce ?


std::getline, puis en mettant la ligne dans un
std::istringstream pour le parser :
ok, j'adopte


Ceci n'est pas du tout garanti par la norme du langage, qui
permet des octets de rembourage n'importe où dans
PDBAtomFmt.
J'imagine que ce qui est litigieux c'est la ligne :

PDBAtomFmt const& atom(
*reinterpret_cast< PDBAtomFmt const* >( line.data() ) ) ;
où l'on fais un alignement brutal entre la chaine de caractère de la
ligne et les chaines de caractères empaquetées dans PDBAtomFmt ?
Question bete : la norme garantit t'elle déjà que dans une structure
les divers champs sont stockés dans l'ordre de précédence où ils
sont déclarés ?
Pour éviter ce genre d'horreurs ne pourrait t'on pas imaginer une
fonction qui se chargerait de la conversion de char const * vers
PDBAtomFmt ?

En général, c'est une bonne idée d'écrire l'application comme
simplement de la colle qui joint des divers composants
Je tacherai de m'en souvenir



Avatar
kanze
meow wrote:
Pour commencer : je définirais une classe Atom, plutôt
[..]

Mais un atome, c'est un atome, ce n'est pas n'importe quelle
Certes. J'ai toujours tendance à vouloir faire vite pour aller

à ce que je juge essentiel : mon algorithme et ses résultats.
Bref, je sous-estime l'importance de certains à coté et mon
code finit par devenir illisible.
hop, je me flagelles un coup et j'ecris ces classes.

Le typage statique est ton ami.
Euh... C'est quoi le typage statique (shame & blush)



C'est la vérification des types par le compilateur. Ici, c'est
par exemple que si une fonction opère sur un atome, on ne lui
passe pas un Quad<double> qui représente autre chose par erreur.

[...]
des fichiers "rayon x y z" à la place de "x y z rayon" ?


Des manipulateurs.


Qu'est- ce ?


Ce sont des types ou des fonctions qui servent à gerer le format
des entrées/sorties. Dans la norme, des fonctions comme
std::setw et d'autres. Ici, tu écriras une fonction qui dirait à
l'extracteur que le rayon précède à la position, par exemple. Ce
qui permettrait d'écrire quelque chose du genre :

source >> rayonPrecede >> atome ;

[...]
Ceci n'est pas du tout garanti par la norme du langage, qui
permet des octets de rembourage n'importe où dans
PDBAtomFmt.


J'imagine que ce qui est litigieux c'est la ligne :
PDBAtomFmt const& atom(
*reinterpret_cast< PDBAtomFmt const* >( line.data() ) ) ;
où l'on fais un alignement brutal entre la chaine de caractère
de la ligne et les chaines de caractères empaquetées dans
PDBAtomFmt ?


Tout à fait. Ce qui n'est pas garantie, c'est qu'il n'y a pas
d'octets supplémentaires de rembourage dans la struct. Comme
j'ai dit, ça marche sur les compilateurs dont je me sers, mais
c'est une garantie assez faible.

Question bete : la norme garantit t'elle déjà que dans une
structure les divers champs sont stockés dans l'ordre de
précédence où ils sont déclarés ?


Dans la mesure où il n'y a pas de spécification de contrôle
d'accès (public, private ou protected), l'ordre est garanti.

Pour éviter ce genre d'horreurs ne pourrait t'on pas imaginer une
fonction qui se chargerait de la conversion de char const * vers
PDBAtomFmt ?


On pourrait. Je ne sais pas si c'est si intéressant.
L'alternatif, assez répandu d'ailleurs, c'est de déclarer
l'offset et la largeur de chaque champs, du genre :

enum PDBAtomFmt
{
offsetX = 0,
lengthX = 8,
offsetY = offsetX + lengthX,
lengthY = 8,
offsetZ = offsetY + lengthY,
lengthZ = 8,
offsetW = offsetZ + lengthZ,
lengthW = 6
} ;

D'où on tire les paramètres pour getFloat.

Sinon, je suis en train de me démander si la meilleur solution
ne serait-elle pas un espèce de traits, du genre :

class PDBAtomFmt
{
public:
static int offsetX() { return 0 ; }
static int lengthX() { return 8 ; }
static int precisionX() { return 3 ; }
// ...
} ;

Voire avec une seule fonction par champs, qui renverrait
directement un objet fonctionnel qui se comportera comme la
fonction getFloat, mais avec les paramètres de l'offset, de la
largeur et de la précision fixés, de façon à n'avoir à lui
donner que l'adresse du début de la chaîne.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34