OVH Cloud OVH Cloud

méthode statique

27 réponses
Avatar
Nicolas Aunai
salut,


je comprends l'intéret d'une variable static dans une classe, mais pas
celui d'une méthode static... que sont-elles et à quoi servent-elles ?

merci

--
Nico,
http://astrosurf.com/nicoastro
messenger : nicolas_aunai@hotmail.com

10 réponses

1 2 3
Avatar
kanze
(Arnaud Debaene) wrote in message
news:...
Julien IBARZ wrote in message
news:<4027f7b6$0$28274$...
Le gros avantage est qu'une méthode statique est en tout point
similaire à une fonction (pointeurs, appels, etc).

Il est généralement nécessaire d'y avoir recours pour des
fonctionnalités encapsulées dans une classe mais nécessitant un
passage de pointeur sur fonction (pour un classe CThread par
exemple).


A moins que je n'ai pas compris votre propos, il est tout à fait
possible de faire des pointeurs vers des méthodes de classes non
statiques.


Oui, mais toutes les APIs de threading que je connais attendent comme
fonction principale d'un thread un pointeur vers une fonction "libre",
qui est nécessairement une fonction statique si on veut la mettre dans
une classe.


La seule que je connais, moi, c'est Posix. Et Posix exige un pointeur
vers une fonction à linkage "C". (Posix ne reconnaît même pas que le C++
existe.) Donc, pas de fonction membre, statique ou non.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16



Avatar
Gourgouilloult

Sans connaître plus, c'est difficile à dire. Surtout, il faudrait savoir
la signature exacte de la fonction à passer en callback.


Ben en fait, je pense que ça ira. Les réponses que tu m'as données sont
excellentes. Merci beaucoup. ;)

Si le callback a un paramètre, la meilleur solution est de s'en servir.
Comme on fait avec pthread_create:
[...]

Note que dans ce cas-ci, il faut absolument une fonction avec linkage
"C". Une fonction membre, static ou non, ne marche pas. (Selon la norme,
je ne crois pas que ça doit compiler. Mais dans la pratique, je ne
connais pas de compilateur qui signale l'erreur si la fonction est
statique. Ce qui donne un comportement indéfini, qui a parfois l'air
même de marcher.)


En effet, j'étais parti sur une fonction membre statique et ça avait
l'air de vachement bien marcher. Mais je ne comprends pas trop bien ce
qu'il y a de différent entre le type d'une fonction membre statique et
le type d'une fonction libre (je ne comprends pas non plus la nécessité
que celle-ci soit statique, d'ailleurs). Je ne vois pas non plus la
nécessité du linkage "C" ; tel que je me figure la chose, extern "C"
détermine la convention de nommage employée. Or, j'aurais dit qu'en
utilisant des pointeurs, on se plaçait au-dela de cette question...

Par contre, pour ce qui de l'assignation du callback, qui se fait par
simple affectation de pointeur, mon collègue, qui gère la portabilité
(de façon empirique), est d'avis de laisser un cast de toute façon. Il
me dit que selon les compilos, voire selon les versions, c'est des
choses plutôt sensibles. Y a du pour ? Du contre ?

Je devrais utiliser le paramètre prévu par SDL pour tenir lieu de this
(mais c'est un void* !- ) ?


C'est normal dans une interface C. Alors, il y a static_cast.


Oki. En fait, je voulais aussi voir si ça n'était pas une solution jugée
crado ;)

Je fais de ma variable globale un membre statique ? (Ah mais non, j'ai
pas prévu d'en faire un pointeur...) Je change ma classe en namespace
? Autre chose ? Ou bien je garde ce que j'ai et j'arrête de poser des
questions bêtes ?-)


La variable globale est à éviter. Si l'interface permet un paramètre à
la fonction, sers-t'en. Sinon, j'aurais tendance à préférer une variable
statique membre, mais il n'y a pas de solution vraiment propre.


Idem pour la donnée membre statique. En plus, ces solutions vont
également arranger les bidons affreux de mon «constructeur en deux
parties» (oui, du coup, la classe avait hérité d'une fonction init(), à
mon grand dégoût).

Gourgou
Encore merci, en tout cas.


Avatar
kanze
Gourgouilloult <gourgou_at_club-internet_point_fr> wrote in message
news:<402c229d$0$6966$...

Si le callback a un paramètre, la meilleur solution est de s'en
servir. Comme on fait avec pthread_create:
[...]

Note que dans ce cas-ci, il faut absolument une fonction avec
linkage "C". Une fonction membre, static ou non, ne marche pas.
(Selon la norme, je ne crois pas que ça doit compiler. Mais dans la
pratique, je ne connais pas de compilateur qui signale l'erreur si
la fonction est statique. Ce qui donne un comportement indéfini, qui
a parfois l'air même de marcher.)


En effet, j'étais parti sur une fonction membre statique et ça avait
l'air de vachement bien marcher. Mais je ne comprends pas trop bien ce
qu'il y a de différent entre le type d'une fonction membre statique et
le type d'une fonction libre (je ne comprends pas non plus la
nécessité que celle-ci soit statique, d'ailleurs).


La différence qui nous intéresse ici, c'est qu'on ne peut pas spécifier
un « linkage » pour une fonction membre statique -- il a forcement le
linkage par défaut, qui est C++. Or, au moins dans le cas de Posix (et
j'imagine que c'est pareil pour Windows), pthread_create est une
fonction C, qui prend comme paramètre l'adresse d'une fonction C, ou en
C++, d'une fonction qui a un linkage "C". Donc :

void* f( void* p ) ;
extern "C" void* g( void* p ) ;
struct S
{
static void* h( void* p ) ;
} ;

pthread_t id ;
pthread_create( &id, NULL, &f, NULL ) ; // illégal
pthread_create( &id, NULL, &g, NULL ) ; // légal
pthread_create( &id, NULL, &S::h, NULL ) ; // illégal

Si le langage permettait un « extern "C" » sur la fonction statique, je
suppose que ça marcherait, mais ce n'est pas le cas.

Le linkage d'une fonction fait partie du type en C++. Du coup, passer
l'adresse d'une fonction à linkage "C++" à une fonction qui attend
l'adresse d'une fonction à linkage "C" est une erreur qui exige un
diagnostique -- si le compilateur le lasse passer sans gueuler, c'est
une erreur dans le compilateur. (Le mien, Sun CC 5.1, se plaint.)

Maintenant, si le compilateur le laisse passer, c'est possible que ça
marche. Sur Sparc, par exemple, il n'y a aucune différence dans les
conventions d'appel d'une fonction "C" et d'une fonction "C++". Sun CC
se plaint, mais il génère le code quand même, et le code marche. Mais ce
n'est pas toujours le cas -- sur l'architecture Intel, je me souviens
que les conventions d'appel étaient différentes en "C" et en "C++", au
moins avec le compilateur Zortech. (Avec VC++, il y a un paquet de
conventions d'appel différentes, et des mots clés non standard pour les
choisir. Mais je crois que le défaut est pareil en C et en C++.)

Je ne vois pas non plus la nécessité du linkage "C" ; tel que je me
figure la chose, extern "C" détermine la convention de nommage
employée.


Il détermine la convention de l'appel : le nommage, mais aussi où se
trouve les paramètres, ou on met l'adresse de rétour, ou on met une
valeur de rétour... Choses qui peuvent être les mêmes d'un langage à
l'autre (ou au moins entre C et C++), mais qui peuvent aussi varier.
Dans le cas de Sparc, la définition de l'architecture du processeur
précise aussi une façon préférée à passer les paramètres, qui est
respecté et par C et par C++. Dans le cas d'Intel, la façon « préférée »
ne marche pas pour des varargs. Or, historiquement, toutes les fonctions
C était des varargs -- la norme C a bien dit que pour que les varargs
fonctionnent, il faut qu'il y a un prototype en vue, mais il y avait
bien de programmes C écrits avant la norme qui faisait des printf sans
inclure <stdio.h>. Tandis que les prototypes ont toujours été de rigueur
pour C++. Une implémentation de qualité pour Intel se servira donc d'une
convention spéciale pour les fonctions C et les fonctions varargs en
C++, et la convention « préférée » pour les autres fonctions de C.

Or, j'aurais dit qu'en utilisant des pointeurs, on se plaçait au-dela
de cette question...


Il faut quand même savoir appeler la fonction correctement.

Par contre, pour ce qui de l'assignation du callback, qui se fait par
simple affectation de pointeur, mon collègue, qui gère la portabilité
(de façon empirique), est d'avis de laisser un cast de toute façon. Il
me dit que selon les compilos, voire selon les versions, c'est des
choses plutôt sensibles. Y a du pour ? Du contre ?


Je dirais qu'il ne faut absolument pas utiliser un cast. Il faut bien
passer une fonction du bon type, pour que ça marche partout, sans cast.
Le cast ne sert qu'à masquer les erreurs, pour qu'elles apparaissent à
l'execution, plutôt qu'à la compilation.

Je devrais utiliser le paramètre prévu par SDL pour tenir lieu de
this (mais c'est un void* !- ) ?


C'est normal dans une interface C. Alors, il y a static_cast.


Oki. En fait, je voulais aussi voir si ça n'était pas une solution
jugée crado ;)


C'est crado dans la mésure que tout utilisation d'une interface C est
crado. Tu ne peux pas être plus pûr que l'API dont tu te sers. (Et si tu
crois que ça c'est crado, tu dois voir ce que je suis amené à faire pour
appeler readdir_r:-). Alors, là, c'est du crado. Mais l'interface est
normée par Posix, et je n'ai pas le droit de la modifier.)

Je fais de ma variable globale un membre statique ? (Ah mais non,
j'ai pas prévu d'en faire un pointeur...) Je change ma classe en
namespace ? Autre chose ? Ou bien je garde ce que j'ai et j'arrête
de poser des questions bêtes ?-)


La variable globale est à éviter. Si l'interface permet un paramètre
à la fonction, sers-t'en. Sinon, j'aurais tendance à préférer une
variable statique membre, mais il n'y a pas de solution vraiment
propre.


Idem pour la donnée membre statique.


Tout à fait. Les variables statiques ne cessent pas de poser des
problèmes en multi-thread.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16



Avatar
Gourgouilloult
Yop, tentative d'implémentation !

extern "C" static void* threadStarter( void* obj )
{
static_cast< Thread* >( obj )->run() ;
return NULL ;
}




Ca râle sur :
extern "C" static /* fonc */
qui semblerait signifier qu'il y a plusieurs classes de stockage (ça,
c'est au moins une mauvaise traduction !-). Ca passe mieux avec :
extern "C" { static /* fonc */ }

La différence qui nous intéresse ici, c'est qu'on ne peut pas spécifier
un « linkage » pour une fonction membre statique -- il a forcement le
linkage par défaut, qui est C++. [...]
Si le langage permettait un « extern "C" » sur la fonction statique, je
suppose que ça marcherait, mais ce n'est pas le cas.


Est-ce que j'aurais tort d'étendre en «le langage ne permet pas de
'extern "C"', pour n'importe quel type de déclaration de fonction à
l'intérieur d'une classe» ? Parce que voila, mon cas est un poil plus
vicieux que ce que tu avais proposé au début :

class Thread
{
public:
void start() ;
virtual void run() = 0 ;

private:
pthread_t myId ;
} ;




Dans mon cas, donc, la fonction membre équivalente à threadStarter() est
appelée par «le système» d'une façon sur laquelle je n'ai aucun contrôle
(on dit asynchrone, je crois ?). Mon équivalent pour run(), qui s'occupe
donc du traitement effectif, n'a aucune raison d'être appelée depuis un
endroit quelconque de mon code, mis à part threadStarter(). J'étais donc
parti sur l'idée d'avoir run() en private. Or, à ce moment, il me
faudrait déclarer threadStarter() en friend de Thread, ce qui, avec son
extern "C", n'a pas l'air envisageable du tout. Du coup, pour l'instant,
mon run() se retrouve en public avec des raisons de ne pas l'être...

Je ne vois pas non plus la nécessité du linkage "C" ; tel que je me
figure la chose, extern "C" détermine la convention de nommage
employée.


Il détermine la convention de l'appel : le nommage, mais aussi où se
trouve les paramètres, ou on met l'adresse de rétour, ou on met une
valeur de rétour [...]


Ah oui, zut, j'aurais dû y penser ! (C'est une question qui m'avait
turlupiné pendant mes cours d'asm, il y a déjà quelques années ;)

Par contre, pour ce qui de l'assignation du callback, qui se fait par
simple affectation de pointeur, mon collègue, qui gère la portabilité
(de façon empirique), est d'avis de laisser un cast de toute façon. Il
me dit que selon les compilos, voire selon les versions, c'est des
choses plutôt sensibles. Y a du pour ? Du contre ?


Je dirais qu'il ne faut absolument pas utiliser un cast. Il faut bien
passer une fonction du bon type, pour que ça marche partout, sans cast.
Le cast ne sert qu'à masquer les erreurs, pour qu'elles apparaissent à
l'execution, plutôt qu'à la compilation.


Noté, merci. (Chouette, un vrai argument ! C'est qu'il est un peu
catégorique sur certaines choses, mon copain ;)

Oki. En fait, je voulais aussi voir si ça n'était pas une solution
jugée crado ;)


C'est crado dans la mésure que tout utilisation d'une interface C est
crado. Tu ne peux pas être plus pûr que l'API dont tu te sers. (Et si tu
crois que ça c'est crado, tu dois voir ce que je suis amené à faire pour
appeler readdir_r:-). Alors, là, c'est du crado. Mais l'interface est
normée par Posix, et je n'ai pas le droit de la modifier.)


Hmm... j'aurais bien demandé à voir par simple masochisme, mais tout
compte fait, le proto ne me parle absolument pas ;)

La variable globale est à éviter. Si l'interface permet un paramètre
à la fonction, sers-t'en. Sinon, j'aurais tendance à préférer une
variable statique membre, mais il n'y a pas de solution vraiment
propre.


Idem pour la donnée membre statique.


Tout à fait. Les variables statiques ne cessent pas de poser des
problèmes en multi-thread.


Ca non plus, je ne me serais pas posé la question. Pourtant on a déjà
quelques problèmes flous de ce côté là, apparemment... Je vais essayer
d'éradiquer les vars globales, mais ça implique de trouver toutes les
utilisations de chacune et d'implémenter une alternative.

A part ça, on parlait d'histoires de constructeur. Si j'arrive à faire
disparaître cette variable globale, ça me permettra de fusionner la
fonction membre init() avec le constructeur. Ce dernier comportera alors
l'affectation du pointeur vers le callback, ainsi que du void* que «le
système» doit lui passer, initialisé, lui, donc, à this. Ma question :
peut-on faire confiance à this dans le corps du constructeur ? (Ou bien
suis-je en train de m'emmêler les pinceaux avec mes vieux souvenirs de
java ?-)

J'ai deux idées là-dessus :
1) passé la liste d'init (l'{ de la définition du ctor, plus
exactement), ça devrait aller ;
2) je vais essayer de vérifier ça tout de suite, mais je ne suis pas
certain de le trouver rapidement (donc je laisse quand même la question ;).

Gourgou
Je te propose un deal : à partir de maintenant, au lieu de dire «merci»
systématiquement, je les garde de côté, et quand j'en ai suffisamment,
j'te fais un tapis avec ;)



Avatar
Gourgouilloult
peut-on faire confiance à this dans le corps du constructeur ? (Ou bien
suis-je en train de m'emmêler les pinceaux avec mes vieux souvenirs de
java ?-)

J'ai deux idées là-dessus :
1) passé la liste d'init (l'{ de la définition du ctor, plus
exactement), ça devrait aller ;


En fait, c'est pire que ça, cf §5.1/3 et surtout §12.6.2/7 :

«[...] Note: because the mem-initializer are evaluated in the scope of
the constructor, the this pointer can be used in the expression-list of
a mem-initializer to refer to the object being initialized.»

Ce qui, quoi qu'indirectement, signifie bien que this dans un
constructeur fait que j'en attendais.

2) je vais essayer de vérifier ça tout de suite, mais je ne suis pas
certain de le trouver rapidement (donc je laisse quand même la question ;).


Oui, honte à moi, je l'ai codé avant d'ouvrir un bouquin ;)

Gourgou

Avatar
James Kanze
Gourgouilloult <gourgou_at_club-internet_point_fr> writes:

|> Yop, tentative d'implémentation !

|> >>> extern "C" static void* threadStarter( void* obj )
|> >>> {
|> >>> static_cast< Thread* >( obj )->run() ;
|> >>> return NULL ;
|> >>> }

|> Ca râle sur :
|> extern "C" static /* fonc */

En effet. Je ne connais pas les règles exactes, mais c'est possible
qu'une fonction « extern "C" » ne peut pas être statique.
(C'est aussi possible qu'il y a une erreur dans le compilateur, qui
traite l'extern ici comme un vrai externe, c-à-d le contraire de
static.)

|> qui semblerait signifier qu'il y a plusieurs classes de stockage
|> (ça, c'est au moins une mauvaise traduction !-). Ca passe mieux
|> avec :

|> extern "C" { static /* fonc */ }

Alors, tu as déjà trouvé la solution pour ton compilateur. Dans
la doute, on pourrait ôter le static complétement.

|> > La différence qui nous intéresse ici, c'est qu'on ne peut
|> > pas spécifier un « linkage » pour une fonction membre
|> > statique -- il a forcement le linkage par défaut, qui est C++.
|> > [...] Si le langage permettait un « extern "C" » sur la
|> > fonction statique, je suppose que ça marcherait, mais ce n'est
|> > pas le cas.

|> Est-ce que j'aurais tort d'étendre en «le langage ne permet
|> pas de 'extern "C"', pour n'importe quel type de déclaration de
|> fonction à l'intérieur d'une classe» ?

En effet. Mais il ne m'étais pas venu à l'ésprit qu'on pouvait
vouloir une fonction member non statique avec linkage "C".

|> Parce que voila, mon
|> cas est un poil plus vicieux que ce que tu avais proposé au
|> début :

|> >>> class Thread
|> >>> {
|> >>> public:
|> >>> void start() ;
|> >>> virtual void run() = 0 ;
|> >>>
|> >>> private:
|> >>> pthread_t myId ;
|> >>> } ;

|> Dans mon cas, donc, la fonction membre équivalente à
|> threadStarter() est appelée par «le système» d'une
|> façon sur laquelle je n'ai aucun contrôle (on dit asynchrone,
|> je crois ?).

C'est en fait aussi le cas de la fonction de démarrage du thread.

|> Mon équivalent pour run(), qui s'occupe donc du traitement
|> effectif, n'a aucune raison d'être appelée depuis un endroit
|> quelconque de mon code, mis à part threadStarter(). J'étais
|> donc parti sur l'idée d'avoir run() en private. Or, à ce
|> moment, il me faudrait déclarer threadStarter() en friend de
|> Thread, ce qui, avec son extern "C", n'a pas l'air envisageable du
|> tout.

Je crois que si :

extern "C" void* threadStarter( void* p )
{
static_cast< Thread*>( p )->run() ;
return NULL ;
}

class Thread
{
friend void* threadStarter( void* ) ;
// ...
} ;

A priori, je crois que ça doit marcher.

|> Du coup, pour l'instant, mon run() se retrouve en public avec des
|> raisons de ne pas l'être...

Bof. Si c'est le seul problème...

[...]
|> >>Oki. En fait, je voulais aussi voir si ça n'était pas une
|> >>solution jugée crado ;)

|> > C'est crado dans la mésure que tout utilisation d'une interface
|> > C est crado. Tu ne peux pas être plus pûr que l'API dont tu
|> > te sers. (Et si tu crois que ça c'est crado, tu dois voir ce
|> > que je suis amené à faire pour appeler readdir_r:-). Alors,
|> > là, c'est du crado. Mais l'interface est normée par Posix,
|> > et je n'ai pas le droit de la modifier.)

|> Hmm... j'aurais bien demandé à voir par simple masochisme,
|> mais tout compte fait, le proto ne me parle absolument pas ;)

Le proto, ce n'est pas le tout. Il y a un paramètre dirent*. Mais
quand on en lit la description, et qu'on régarde dans l'en-tête,
il se trouve que dirent, c'est :

struct dirent
{
// ...
char d_name[ 1 ] ;
} ;

et que la description de la fonction exige qu'en fait, il y a assez de
place derrière pour mettre le nom.

En fait, ce qu'ils veulent, c'est :

struct dirent
{
// ...
char d_name[] ;
} ;

Seulement, ce n'était pas légal quand ils ont formulé leur
norme (et ce n'est toujours pas légal en C++). Alors, il se sont
servi d'un ancien hack, illégal selon la norme C, mais qui fonctionne
à peu près partout, c-à-d qu'il s'attend à ce qu'on fasse :

dirent* p = (dirent*)malloc( sizeof( dirent ) + N ) ;

|> Gourgou

|> Je te propose un deal : à partir de maintenant, au lieu de dire
|> «merci» systématiquement, je les garde de côté, et
|> quand j'en ai suffisamment, j'te fais un tapis avec ;)

Ce n'est pas la peine. J'imagine toujours qu'un jour ou l'autre, il se
peut que j'ai à maintenir du code que tu as écrit. Alors,
écris-le bien, et je serais content.

--
James Kanze mailto:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France +33 1 41 89 80 93
Avatar
Gourgouilloult

Ca râle sur :
extern "C" static /* fonc */


En effet. Je ne connais pas les règles exactes, mais c'est possible
qu'une fonction « extern "C" » ne peut pas être statique.
(C'est aussi possible qu'il y a une erreur dans le compilateur, qui
traite l'extern ici comme un vrai externe, c-à-d le contraire de
static.)

Ca passe mieux avec :
extern "C" { static /* fonc */ }


Alors, tu as déjà trouvé la solution pour ton compilateur. Dans
la doute, on pourrait ôter le static complétement.


(Je m'étonne un peu qu'un plus chevelu que moi n'ait pas encore
relevé..) En fait, la soluce en question est inspirée d'un passage du
TC++L, que je vais essayer de retrouver pour le coup... dans §9.2.4 :

« Any declaration can appear within a linkage block :

extern "C" { // any declaration here, for example:
int g1; // definition
extern int g2; // declaration, not definition
}

In particular, the scope and storage class of variables are not
affected, so g1 is still a global variable --- and is still defined
rather than just declared. To declare but not define variable, you must
apply the keyword extern directly in the declaration. For example :

extern "C" int g3; // declaration, not definition

This looks odd at first glance. However, it is a simple consequence of
keeping the meaning unchanged when adding "C" to an extern declaration
and the meaning of a file unchanged when enclosing it in a linkage block. »

Et comme mon édition n'est plus trop récente (enfin, selon mes critères,
toujours ;-P ), on va aussi vérifier dans mon nouveau bouquin.
...
Ce fut un peu plus dur, mais §7.5/7 dit à peu près la même chose (en un
peu moins clair bien sûr ;)

La différence qui nous intéresse ici, c'est qu'on ne peut
pas spécifier un « linkage » pour une fonction membre
statique -- il a forcement le linkage par défaut, qui est C++.


Est-ce que j'aurais tort d'étendre en «le langage ne permet
pas de 'extern "C"', pour n'importe quel type de déclaration de
fonction à l'intérieur d'une classe» ?


En effet. Mais il ne m'étais pas venu à l'ésprit qu'on pouvait
vouloir une fonction member non statique avec linkage "C".


En fait, je n'étais pas allé jusqu'à vraiment creuser non plus ce point
là. Le cas qui m'intéressait surtout, c'était friend. Comme je crois me
souvenir qu'il est un peu bancal d'assimiler friend et déclaration, j'ai
préféré extrapoler un peu plus large ;)

Dans mon cas, donc, la fonction membre équivalente à
threadStarter() est appelée par «le système» d'une
façon sur laquelle je n'ai aucun contrôle (on dit asynchrone,
je crois ?).


C'est en fait aussi le cas de la fonction de démarrage du thread.


La différence que je faisais, c'est qu'on déclenche quand même l'appel à
threadStarter() en passant explicitement par Thread::start(). Dans mon
cas, SDL prend le pointeur de fonction et s'en sert quand elle en a
besoin (pour remplir son buffer de son, en l'occurence).

Etant donné que je ne suis pas resté penché sur le problème plus
longtemps que ça, je ne suis plus trop certain de voir en quoi ça ferait
une grosse différence.

donc parti sur l'idée d'avoir run() en private. Or, à ce
moment, il me faudrait déclarer threadStarter() en friend de
Thread, ce qui, avec son extern "C", n'a pas l'air envisageable du
tout.


Je crois que si :

extern "C" void* threadStarter( void* p )
{
static_cast< Thread*>( p )->run() ;
return NULL ;
}

class Thread
{
friend void* threadStarter( void* ) ;
// ...
} ;

A priori, je crois que ça doit marcher.


Ou bien quelque chose comme :

class Thread {
extern "C" { friend void* threadStarter (void*); }
};

... on voit des exemples de ce genre de choses qui font un peur, dans la
norme ;)

Du coup, pour l'instant, mon run() se retrouve en public avec des
raisons de ne pas l'être...


Bof. Si c'est le seul problème...


Oui, chose incroyable venant de moi, j'ai préféré la solution qui marche
bien à la recherche acharnée d'une solution à un ennui mineur de
conception !

C'est crado dans la mésure que tout utilisation d'une interface
C est crado. Tu ne peux pas être plus pûr que l'API dont tu
te sers. (Et si tu crois que ça c'est crado, tu dois voir ce
que je suis amené à faire pour appeler readdir_r:-). Alors,
là, c'est du crado. Mais l'interface est normée par Posix,
et je n'ai pas le droit de la modifier.)


Hmm... j'aurais bien demandé à voir par simple masochisme,
mais tout compte fait, le proto ne me parle absolument pas ;)


Le proto, ce n'est pas le tout. Il y a un paramètre dirent*. Mais
quand on en lit la description, et qu'on régarde dans l'en-tête,
il se trouve que dirent, c'est :

struct dirent
{
// ...
char d_name[ 1 ] ;
} ;

et que la description de la fonction exige qu'en fait, il y a assez de
place derrière pour mettre le nom.

En fait, ce qu'ils veulent, c'est :

struct dirent
{
// ...
char d_name[] ;
} ;

Seulement, ce n'était pas légal quand ils ont formulé leur
norme (et ce n'est toujours pas légal en C++). Alors, il se sont
servi d'un ancien hack, illégal selon la norme C, mais qui fonctionne
à peu près partout, c-à-d qu'il s'attend à ce qu'on fasse :

dirent* p = (dirent*)malloc( sizeof( dirent ) + N ) ;


Oui, je me souviens de ce truc, on m'avait montré ça «à l'école» ;)
Effectivement, c'est un peu goret, mais je ne savais pas que c'était
illégal. Et, si je peux demander, tu fais comment ? Tu continues à faire
comme ça, ou tu as une astuce avec new ?-)

Gourgou
(Bon, en retard, pour changer...)



Avatar
kanze
Gourgouilloult <gourgou_at_club-internet_point_fr> wrote in message
news:<40324410$0$5917$...

Ca râle sur :
extern "C" static /* fonc */


En effet. Je ne connais pas les règles exactes, mais c'est possible
qu'une fonction « extern "C" » ne peut pas être statique. (C'est
aussi possible qu'il y a une erreur dans le compilateur, qui traite
l'extern ici comme un vrai externe, c-à-d le contraire de static.)

Ca passe mieux avec :
extern "C" { static /* fonc */ }


Alors, tu as déjà trouvé la solution pour ton compilateur. Dans la
doute, on pourrait ôter le static complétement.


(Je m'étonne un peu qu'un plus chevelu que moi n'ait pas encore
relevé..)


C'est la paresse:-). J'ai une copie de la norme en ligne ; j'aurais
facilement pû vérifier, mais j'avais la flegme.

En fait, la soluce en question est inspirée d'un passage du TC++L, que
je vais essayer de retrouver pour le coup... dans §9.2.4 :

« Any declaration can appear within a linkage block :

extern "C" { // any declaration here, for example:
int g1; // definition
extern int g2; // declaration, not definition
}

In particular, the scope and storage class of variables are not
affected, so g1 is still a global variable --- and is still defined
rather than just declared. To declare but not define variable, you must
apply the keyword extern directly in the declaration. For example :

extern "C" int g3; // declaration, not definition

This looks odd at first glance. However, it is a simple consequence of
keeping the meaning unchanged when adding "C" to an extern declaration
and the meaning of a file unchanged when enclosing it in a linkage
block. »


Donc, « extern "C" static » serait illégal. C'est logique, d'après une
façon, même si l'extern dans « extern "C" » a une signification
supplémentaire.

Et comme mon édition n'est plus trop récente (enfin, selon mes
critères, toujours ;-P ), on va aussi vérifier dans mon nouveau
bouquin.
...
Ce fut un peu plus dur, mais §7.5/7 dit à peu près la même chose (en
un peu moins clair bien sûr ;)

La différence qui nous intéresse ici, c'est qu'on ne peut pas
spécifier un « linkage » pour une fonction membre statique -- il
a forcement le linkage par défaut, qui est C++.


Est-ce que j'aurais tort d'étendre en «le langage ne permet pas de
'extern "C"', pour n'importe quel type de déclaration de fonction
à l'intérieur d'une classe» ?


En effet. Mais il ne m'étais pas venu à l'ésprit qu'on pouvait
vouloir une fonction member non statique avec linkage "C".


En fait, je n'étais pas allé jusqu'à vraiment creuser non plus ce
point là. Le cas qui m'intéressait surtout, c'était friend. Comme je
crois me souvenir qu'il est un peu bancal d'assimiler friend et
déclaration, j'ai préféré extrapoler un peu plus large ;)

Dans mon cas, donc, la fonction membre équivalente à
threadStarter() est appelée par «le système» d'une façon sur
laquelle je n'ai aucun contrôle (on dit asynchrone, je crois ?).


C'est en fait aussi le cas de la fonction de démarrage du thread.


La différence que je faisais, c'est qu'on déclenche quand même l'appel
à threadStarter() en passant explicitement par Thread::start(). Dans
mon cas, SDL prend le pointeur de fonction et s'en sert quand elle en
a besoin (pour remplir son buffer de son, en l'occurence).


Il faut sans doute aussi passer par une fonction quelque part pour que
l'appel de ta fonction a lieu. Ne serait-ce que parce que sinon, le
système n'en connaît pas son adresse. Et ce n'est pas parce qu'on a
exécuté pthread_create (ou son équivalent sous Windows) que
threadStarter va être appelé immédiatement. Le thread est créé, et est
schédulé pour l'exécution -- ensuite, tout dépend de l'algorithme de
schéduling et des priorités.

Etant donné que je ne suis pas resté penché sur le problème plus
longtemps que ça, je ne suis plus trop certain de voir en quoi ça
ferait une grosse différence.


Il n'en fait aucune. (À distinguer du cas où tu passes l'adresse d'une
fonction dans le modèle visiteur, par exemple.)

donc parti sur l'idée d'avoir run() en private. Or, à ce moment,
il me faudrait déclarer threadStarter() en friend de Thread, ce
qui, avec son extern "C", n'a pas l'air envisageable du tout.


Je crois que si :

extern "C" void* threadStarter( void* p )
{
static_cast< Thread*>( p )->run() ;
return NULL ;
}

class Thread
{
friend void* threadStarter( void* ) ;
// ...
} ;

A priori, je crois que ça doit marcher.


Ou bien quelque chose comme :

class Thread {
extern "C" { friend void* threadStarter (void*); }
};


C'est légal, ça ? Je ne le savais pas. Il ne me serait pas venu à
l'ésprit de faire un « extern "C" » autre part qu'à la portée globale.
En fait, je crois que je préfère surtout quelque chose du genre :

extern "C" {
void* threadStarter( void* p )
{
static_cast< Thread* >( p )->run() ;
return NULL ;
}
}

class Thread
{
friend void* threadStarter( void* ) ;
// ...
} ;

Au moins que je rend la fonction « run » public, et je laisse tombé le
« friend ».

... on voit des exemples de ce genre de choses qui font un peur, dans
la norme ;)


Ce n'est pas parce que c'est permis que c'est forcément bon:-).

Du coup, pour l'instant, mon run() se retrouve en public avec des
raisons de ne pas l'être...


Bof. Si c'est le seul problème...


Oui, chose incroyable venant de moi, j'ai préféré la solution qui
marche bien à la recherche acharnée d'une solution à un ennui mineur
de conception !


Pour être valable, une solution doit remplir plusieurs critères : il
faut qu'elle marche (évidemment) et il faut que les autres puissent la
comprendre (de préfère sans avoir récours aux commentaires en ce qui
concerne l'implémentation, mais il est plus important d'avoir une
solution qui marche que d'éviter les commentaires) et la modifier.

Rendre « run() » privé, c'est un bon moyen de documenter qu'il ne doit
pas être appelé en dehors de la classe. Si ça pose de problèmes
techniques par ailleurs, en revanche, il ne faut pas s'acharner.

C'est crado dans la mésure que tout utilisation d'une interface
C est crado. Tu ne peux pas être plus pûr que l'API dont tu te
sers. (Et si tu crois que ça c'est crado, tu dois voir ce que je
suis amené à faire pour appeler readdir_r:-). Alors, là, c'est
du crado. Mais l'interface est normée par Posix, et je n'ai pas
le droit de la modifier.)


Hmm... j'aurais bien demandé à voir par simple masochisme, mais
tout compte fait, le proto ne me parle absolument pas ;)


Le proto, ce n'est pas le tout. Il y a un paramètre dirent*. Mais
quand on en lit la description, et qu'on régarde dans l'en-tête, il
se trouve que dirent, c'est :

struct dirent
{
// ...
char d_name[ 1 ] ;
} ;

et que la description de la fonction exige qu'en fait, il y a assez
de place derrière pour mettre le nom.

En fait, ce qu'ils veulent, c'est :

struct dirent
{
// ...
char d_name[] ;
} ;

Seulement, ce n'était pas légal quand ils ont formulé leur norme (et
ce n'est toujours pas légal en C++). Alors, il se sont servi d'un
ancien hack, illégal selon la norme C, mais qui fonctionne à peu
près partout, c-à-d qu'il s'attend à ce qu'on fasse :

dirent* p = (dirent*)malloc( sizeof( dirent ) + N ) ;


Oui, je me souviens de ce truc, on m'avait montré ça «à l'école» ;)
Effectivement, c'est un peu goret, mais je ne savais pas que c'était
illégal.


En fait, certains qui ont voté la norme C ne se rendaient pas compte non
plus, voir http://www.lysator.liu.se/c/rat/c5.html#3-5-4-2. Pour
d'autres, en revanche, c'était bien l'intention de permettre à une
implémentation de vérifier les bornes du tableau, telles qu'elles
apparaissaient dans la déclaration.

Je sais que par la suite, il y a eu beaucoup de discussions, assez
chaudes d'ailleurs, sur cette question, ainsi que la question si :

int a[5][5] ;
int* p = &a[0][0] ;
p[ 6 ] = 1 ;

était légal. À mon avis, la norme (C90) disait non, mais il faisait
assez de garanties en ce qui concernait la disposition en mémoire pour
que la réponse ne soit pas claires.

D'après mes souvenirs, il y a eu une démande de clarification sur ses
points, avec la réponse qu'ils étaient illégale. Mais j'avoue ne pas
avoir vu le texte officiel -- je me base sur mes souvenirs (pas toujours
parfaits) d'une discussion que j'ai eu avec Tom Plum.

Et, si je peux demander, tu fais comment ? Tu continues à faire comme
ça, ou tu as une astuce avec new ?-)


En C++, le suivant est légal :

class S
{
public:
static S* create( size_t n )
{
return new( n ) S( n ) ;
}

char* buffer()
{
return reinterpret_cast< char* >( this + 1 ) ;
}

void operator delete( void* p )
{
::operator delete( p ) ;
}

private:
S( size_t n ) : myN( n ) {}
void* operator new( size_t n, size_t extra )
{
return ::operator new( n + extra ) ;
}
// données fixes...
size_t myN ;
} ;

Il dépend d'une reinterpret_cast, qui est défini par l'implémentation,
mais dont la norme dit plus ou moins que l'intention ici est qu'il donne
le résultat voulu.

Et il ne marche que pour des buffer's de type char, unsigned char et
signed char. (L'implémentation de std::string dans G++ l'utilise à tort,
avec le résultat qu'une std::basic_string<double> fait des core dump
chez moi.) Et en passant, je crois que c'est moi qui a inventé la
technique, et c'est Steve Clamage qui m'a montré pourquoi il ne marche
que pour les types caractères.

Enfin, dans mon cas, il s'agit d'une API C que j'utilise, et je pars du
principe qu'un compilateur qui supporte cette interface va permettre la
saleté avec struct et malloc. Pour l'instant -- j'imagine qu'une version
future le remplace par des VLA, qui sont en fait la réponse du comité C
à comment faire dans ce cas si le bricolage avec struct et malloc est
illégal. La struct est actuellement déclarée :

typedef struct dirent {
ino_t d_ino; /* "inode number" of entry */
off_t d_off; /* offset of disk directory entry */
unsigned short d_reclen; /* length of this record */
char d_name[1]; /* name of file */
} dirent_t;

On ôte le '1' dans la déclaration du tableau à la fin, et on a quelque
chose de parfaitement légal en C99. (Et une API C qui ne peut pas servir
depuis C++. Mais j'ai comme une impression que ceux qui s'occupent de
Posix s'en fout -- que pour eux, le langage C++ n'existe pas.)

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16




Avatar
magesh
wrote:
Anubis wrote in message
news:<c08k47$siq$...

James Kanze wrote:



Nicolas Aunai writes:




je comprends l'intéret d'une variable static dans une classe,
mais pas celui d'une méthode static... que sont-elles et à quoi
servent-elles ?





Elles sont des fonctions membres auxquelles on ne passe pas de
pointeur this.




Le fait qu'elles n'aient pas le pointeur this par défaut en fait
surtout des fonctions (et non pas des méthodes) qui sont simplement
encapsulées dans la classe comme des fonctions le seraient dans un
namespace.



Elles ont bien accès à la partie privée de la classe. Elles font partie
integrale de l'interface de la classe.


Le gros avantage est qu'une méthode statique est en tout point
similaire à une fonction (pointeurs, appels, etc).



Le gros avantage, par rapport à une fonction non-membre, c'est qu'elle a
accès à la partie privée de la classe. Le gros avantage, par rapport à
une fonction membre non-statique, c'est qu'elle peut être appelée sans
instance de la classe.


Il est généralement nécessaire d'y avoir recours pour des
fonctionnalités encapsulées dans une classe mais nécessitant un
passage de pointeur sur fonction (pour un classe CThread par exemple).



C'est en effet une des utilisations importantes, mais pas la seule. Je
dirais que la vaste majorité de mes fonctions statiques sont ou bien des
fonctions usine d'une sorte ou une autre, y comprise l'instance() du
singleton, ou bien des fonctions de « prétraitement » des paramètres
d'un constructeur. J'ai aussi un ou deux cas particuliers, qui me
servent dans une RTTI portable fait maison. Et une fois, évidemment, la
fonction que je passe à pthread_create. (Mais je fais des serveurs, sans
GUI. Je crois que dans les GUI, il y a beaucoup plus de callback.)


Le gros interet que je vois et que j'etais amene a exploiter etait
souvent pour des besoins d'initialisation (one time init partager par tt
les instances) par exemple de les classes wrapper de connexions
(souvent y a beaucoup de variable d'environnement)de base de donnee.
L'idee serait de recuperer le donnees au demarrage(dans un programme
concurrentiel) est initialiser a l'aide de la methode statique avant de
rentrer dans le mode concurrentiel (multitache ou multithread).
Sans methode statique, c'est tout simplement pas faisable d'une maniere
elegante a ce que je sache!!
magesh


--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16





Avatar
Gabriel Dos Reis
writes:

| Si le callback a un paramètre, la meilleur solution est de s'en servir.
| Comme on fait avec pthread_create:
|
| class Thread
| {
| public:
| void start() ;
| virtual void run() = 0 ;

Je note avec intérêt que tu montres des classes avec des fonctions
virtuelles publiques ;-)

-- Gaby
1 2 3