OVH Cloud OVH Cloud

[newbie] curlpp functor

14 réponses
Avatar
none
Bonjour,

Je débute en C++. J'essaye d'écrire un petit programme utilisant CulrPP
et j'ai un probleme avec la classe cURLpp::Types::WriteFunctionFunctor

je la définit comme suit
cURLpp::Types::WriteFunctionFunctor functor(WriteMemoryCallback);

et la fonction WriteMemoryCallback :
size_t WriteMemoryCallback(char* ptr, size_t size, size_t nmemb) {
string strTmp(ptr,size);
buffer += strTmp;
return size*nmemb;
};

Ca fonctionne très bien tant que WriteMemoryCallback est une simple
fonction. Par contre, si je l'encapsule dans une classe, GCC me renvoie :
g++ -Wall -O2 -I/usr/include -c ButineurVirtuel.cpp -o
obj/Release/ButineurVirtuel.o
ButineurVirtuel.cpp: In member function «std::string
ButineurVirtuel::load(std::string)»:
ButineurVirtuel.cpp:22: erreur: no matching function for call to
«utilspp::Functor<unsigned int, utilspp::tl::TypeList<char*,
utilspp::tl::TypeList<unsigned int, utilspp::tl::TypeList<unsigned int,
utilspp::NullType> > > >::Functor(<unknown type>)»
/usr/local/include/utilspp/functor/Functor.inl:29: note: candidats sont:
utilspp::Functor<R, TList>::Functor(Fun) [with Fun = size_t
(ButineurVirtuel::*)(char*, size_t, size_t), R = unsigned int, TList =
utilspp::tl::TypeList<char*, utilspp::tl::TypeList<unsigned int,
utilspp::tl::TypeList<unsigned int, utilspp::NullType> > >]
/usr/local/include/utilspp/functor/Functor.inl:40: note:
utilspp::Functor<R, TList>::Functor(const utilspp::Functor<R, TList>&)
[with R = unsigned int, TList = utilspp::tl::TypeList<char*,
utilspp::tl::TypeList<unsigned int, utilspp::tl::TypeList<unsigned int,
utilspp::NullType> > >]
Process terminated with status 1 (0 minutes, 7 seconds)
3 errors, 0 warnings

D'avance merci,
TSalm




Les codes complets: Le premier listing fonctionne, l'autre non:
=========================DEBUT=============================
#include "main.h"
#include <curlpp/cURLpp.hpp>
#include <curlpp/Easy.hpp>
#include <curlpp/Options.hpp>
#include <curlpp/Exception.hpp>

#define MESS(x) std::cout << x << std::endl

using namespace std;
using namespace cURLpp;

#define MAX_FILE_LENGTH 20000

std::string Buffer;

// Muss statisch sein, sonst schlägt das linken fehl
size_t WriteMemoryCallback(char* ptr, size_t size, size_t nmemb)
{
Buffer += ptr;
return size*nmemb;
};


void print()
{
std::cout << "Length: " << std::endl << Buffer.length() << std::endl;
std::cout << "Content: " << std::endl << Buffer << std::endl;
}


int main(int argc, char *argv[])
{
if(argc != 2) {
std::cerr << "Example 05: Wrong number of arguments" << std::endl
<< "Example 05: Usage: example05 url"
<< std::endl;
return EXIT_FAILURE;
}
char *url = argv[1];

try {
cURLpp::Cleanup cleaner;
cURLpp::Easy request;

// Das writer-Callback setzten, damit
// cURL in einen std::string schreibt
cURLpp::Types::WriteFunctionFunctor functor(WriteMemoryCallback);
cURLpp::Options::WriteFunction *test = new
cURLpp::Options::WriteFunction(functor);
request.setOpt(test);

// Die URL setzten, die emfangen werden soll
request.setOpt(new cURLpp::Options::Url(url));
request.setOpt(new cURLpp::Options::Verbose(false));
request.perform();

//print();
}
catch ( cURLpp::LogicError & e ) {
std::cout << e.what() << std::endl;
}
catch ( cURLpp::RuntimeError & e ) {
std::cout << e.what() << std::endl;
}
}
=========================FIN=============================


=========================DEBUT(ne fonctionne pas)========
#include <iostream>
#include <string>

#include <curlpp/cURLpp.hpp>
#include <curlpp/Easy.hpp>
#include <curlpp/Options.hpp>
#include <curlpp/Exception.hpp>


class ButineurVirtuel {
private:
std::string buffer;

public:
ButineurVirtuel();

std::string load(std::string url);

~ButineurVirtuel();

private:
size_t WriteMemoryCallback(char*, size_t , size_t );
cURLpp::Cleanup cleaner;
cURLpp::Easy request;

};


using namespace std;

// constructeur
ButineurVirtuel::ButineurVirtuel() {
buffer = "";
};

//// fonction interne
size_t ButineurVirtuel::WriteMemoryCallback(char* ptr, size_t size,
size_t nmemb) {
string strTmp(ptr,size);
buffer += strTmp;
return size*nmemb;
};

// charge la page :
std::string ButineurVirtuel::load(std::string url) {
buffer = "";


cURLpp::Types::WriteFunctionFunctor functor(WriteMemoryCallback);
cURLpp::Options::WriteFunction *test = new
cURLpp::Options::WriteFunction(functor);
request.setOpt(test);

request.setOpt(new cURLpp::Options::Url(url));
request.setOpt(new cURLpp::Options::Verbose(false));
request.perform();

//delete test;

return buffer;
};

// destructeur :
ButineurVirtuel::~ButineurVirtuel() {}


int main() {
cout << "hello" << endl;
};

=========================FIN=============================

10 réponses

1 2
Avatar
Sylvain
none wrote on 01/10/2006 22:24:

j'ai un probleme avec la classe cURLpp::Types::WriteFunctionFunctor

cURLpp::Types::WriteFunctionFunctor functor(WriteMemoryCallback);
size_t WriteMemoryCallback(char* ptr, size_t size, size_t nmemb){..};

Ca fonctionne très bien tant que WriteMemoryCallback est une simple
fonction. Par contre, si je l'encapsule dans une classe, GCC renvoie:
[...] erreur: no matching function for call [...]


en effet, "l'encapsulation" dans une classe change la visibilité.

class ButineurVirtuel {
public:
ButineurVirtuel();
std::string load(std::string url);
~ButineurVirtuel();
private:
*static* size_t WriteMemoryCallback(char*, size_t , size_t );

};


une callback doit être static.

Sylvain.

Avatar
James Kanze
Sylvain wrote:
none wrote on 01/10/2006 22:24:

j'ai un probleme avec la classe cURLpp::Types::WriteFunctionFunctor

cURLpp::Types::WriteFunctionFunctor functor(WriteMemoryCallback);
size_t WriteMemoryCallback(char* ptr, size_t size, size_t nmemb){..};

Ca fonctionne très bien tant que WriteMemoryCallback est une simple
fonction. Par contre, si je l'encapsule dans une classe, GCC renvoie:
[...] erreur: no matching function for call [...]


en effet, "l'encapsulation" dans une classe change la visibilité.


None seulement la visibilité. Une fonction membre non statique a
un paramètre de plus, le pointeur this. Et il faut l'appeler
avec une syntaxe différente, avec un object.

class ButineurVirtuel {
public:
ButineurVirtuel();
std::string load(std::string url);
~ButineurVirtuel();
private:
*static* size_t WriteMemoryCallback(char*, size_t , size_t );

};


une callback doit être static.


Typiquement, il faut aussi qu'elle soit « extern "C" », ce qui
exclut des fonctions membres statiques aussi. Il faut qu'elle
soit globale.

--
James


Avatar
Manuel Zaccaria
James Kanze a écrit:

Sylvain wrote:
[...]

une callback doit être static.


Typiquement, il faut aussi qu'elle soit « extern "C" », ce qui
exclut des fonctions membres statiques aussi. Il faut qu'elle
soit globale.


Bonsoir James,

J'ai cru comprendre dans un autre post qu'on pouvait passer
par un typedef 'magique' si on avait vraiment besoin d'une
fonction membre à la fois «static» et «extern "c"».

Je m'explique.

Kevlin Henney (www.curbralan.com), contributeur de boost, a commi:
http://www.two-sdg.demon.co.uk/curbralan/papers/accu/C++Threading.pdf et
http://www.two-sdg.demon.co.uk/curbralan/papers/accu/MoreC++Threading.pdf

J'aime bien cette façon de permettre au compilateur de vérifier les types
jusqu'au bout. Cependant, dans le code suivant (copié-collé-commenté) :

template<typename nullary_function>
struct return_type
{
typedef typename nullary_function::result_type type;
};
template<typename function_result_type>
struct return_type<function_result_type (*)()>
{
typedef function_result_type type;
};

//...

class threader
{
public:
template<typename threadable>
joiner<return_type<threadable>::type> operator()(threadable function)
{
typedef threaded<threadable> threaded;
thread_t handle;

// ici, «thread_create» attend une fonction «extern "C"», <== // c'est embêtant car on veut lui passer «threaded::needle».

if(!thread_create( &handle, 0,
threaded::needle,
new threaded(function)))
throw bad_thread();

return joiner<return_type<threadable>::type>(handle);
}
private:
template<typename threadable>
class threaded
{
public:
explicit threaded(threadable main) : main(main)
{
}

static void *needle(void *eye)
{
// ne peut pas être une fonction libre car <== // dépendant du paramètre de template.

std::auto_ptr<threaded> that(static_cast<threaded *>(eye));
return new return_type<threadable>::type(that->main());
}
private:
threadable main;
};
};


Ce code est donc illégal et ce serait bien dommage s'il n'y avait
pas de solution. A titre d'exercice, J'ai implémenté «threader»
de cette façon après avoir lu un truc (de toi?) sur c.l.c++.m.

Simplifié ça donne:


extern "C" typedef void* (thread_function_t)(void*);
extern "C" typedef thread_function_t* thread_start_address_t;

class threader
{
public:
template<typename threadable>
joiner<return_type<threadable>::type> operator()(threadable function)
{
typedef threaded<threadable> threaded;
thread_t handle;

if(!thread_create( &handle, 0,
threaded::needle,
new threaded(function)))
throw bad_thread();

return joiner<return_type<threadable>::type>(handle);
}
private:
template<typename threadable>
class threaded
{
public:
explicit threaded(threadable main) : main(main)
{
}

// déclare une fonction membre statique extern "C" ? <== static thread_function_t needle;

private:
threadable main;
};
};

// définit une fonction membre statique extern "C" ? <==
template<typename threadable>
void* threader::threaded<threadable>::needle(void *eye)
{
std::auto_ptr<threaded> that(static_cast<threaded *>(eye));
return new return_type<threadable>::type(that->main());
}

Est-ce legal de cette façon ?
En tout cas ça compile sans un warning avec VC 2005.


Cordialement,
Manuel Zaccaria


Avatar
James Kanze
Manuel Zaccaria wrote:
James Kanze a écrit:

Sylvain wrote:
[...]

une callback doit être static.


Typiquement, il faut aussi qu'elle soit « extern "C" », ce qui
exclut des fonctions membres statiques aussi. Il faut qu'elle
soit globale.


J'ai cru comprendre dans un autre post qu'on pouvait passer
par un typedef 'magique' si on avait vraiment besoin d'une
fonction membre à la fois «static» et «extern "c"».


Non. Rien qu'à partir du concepte, c'est impossible. Rien ne
dit que les deux langages passent leurs paramètres de la même
façon : le C++ pourrait les passer dans les registres, par
exemple, et le C sur la pile. (Si de telles différences me
semblent peu probables, j'ai déjà vu le cas sur Intel où la
responsibilité du nettoyage de la pile n'était pas la même.) Or,
si celui qui utilise le pointeur s'attend à une fonction C, il
va passer les paramètres comme le veut le C. Et ta fonction C++
va avoir du mal à s'y retrouver.

Je m'explique.

Kevlin Henney (www.curbralan.com), contributeur de boost, a commi:
http://www.two-sdg.demon.co.uk/curbralan/papers/accu/C++Threading.pdf et
http://www.two-sdg.demon.co.uk/curbralan/papers/accu/MoreC++Threading.pdf

J'aime bien cette façon de permettre au compilateur de
vérifier les types jusqu'au bout. Cependant, dans le code
suivant (copié-collé-commenté) :

template<typename nullary_function>
struct return_type
{
typedef typename nullary_function::result_type type;
};
template<typename function_result_type>
struct return_type<function_result_type (*)()>
{
typedef function_result_type type;
};

//...

class threader
{
public:
template<typename threadable>
joiner<return_type<threadable>::type> operator()(threadable function)
{
typedef threaded<threadable> threaded;
thread_t handle;

// ici, «thread_create» attend une fonction «extern "C"», <===
// c'est embêtant car on veut lui passer «threaded::needle».

if(!thread_create( &handle, 0,
threaded::needle,
new threaded(function)))


En effet. La norme exige ici une erreur, et certains
compilateurs (Sun CC, par exemple) le génèrent.

Il y a d'autres solutions ; regarde, par exemple, comment fait
boost.threads. Sinon, la solution classique, c'est bien
d'utiliser une classe abstraite de base ; il y a bien une
conversion void* en AbstractThread* dans la fonction qu'appelle
le système, mais cette fonction est dans le sous-système des
threads. On assure donc une correction de type vis-à-vis des
utilisateurs.

throw bad_thread();

return joiner<return_type<threadable>::type>(handle);
}
private:
template<typename threadable>
class threaded
{
public:
explicit threaded(threadable main) : main(main)
{
}

static void *needle(void *eye)
{
// ne peut pas être une fonction libre car < ===
// dépendant du paramètre de template.


Elle pourrait être une fonction libre templatée, sauf qu'une
fonction templatée ne peut pas être « extern "C" » non plus.
Mais...

std::auto_ptr<threaded> that(static_cast<threaded *>(eye));


Du moment qu'il passe bien par un transtypage explicit et non
contrôlé, je ne vois aucun problème avec la solution fonction
libre et classe abstraite de base. Il suffit que sa fonction
main (ci-dessous) soit virtuelle dans AbstractThread, et que la
classe threaded dérive de cette classe.

Aussi, en passant, l'utilisation d'auto_ptr ici ne marche pas
avec tous les compilateurs ; le destructeur n'en serait pas
appelé par g++, par exemple, en cas de pthread_cancel.

return new return_type<threadable>::type(that->main());


Note que le type de retour passe également par un void*, et que
donc, il n'y a aucun problème non plus avec ce code dans une
fonction libre « extern "C" ».

}
private:
threadable main;
};
};

Ce code est donc illégal et ce serait bien dommage s'il n'y avait
pas de solution.


Je ne vois rien dans ce code qui exige que la fonction soit
membre ; l'utilisation d'une classe abstraite de base fonctionne
aussi bien.

titre d'exercice, J'ai implémenté «threader»
de cette façon après avoir lu un truc (de toi?) sur c.l.c++.m.


Je doute que ce soit de moi. Par hazard, je suis une des rares
(apparamment) personnes qui se sont réelement servies d'un
compilateur où ça faisait une différence réele ; je suis donc
assez sensibilisé sur le problème.

Bien que...

Simplifié ça donne:

extern "C" typedef void* (thread_function_t)(void*);
extern "C" typedef thread_function_t* thread_start_address_t;

class threader
{
public:
template<typename threadable>
joiner<return_type<threadable>::type> operator()(threadable function)
{
typedef threaded<threadable> threaded;
thread_t handle;

if(!thread_create( &handle, 0,
threaded::needle,
new threaded(function)))
throw bad_thread();

return joiner<return_type<threadable>::type>(handle);
}
private:
template<typename threadable>
class threaded
{
public:
explicit threaded(threadable main) : main(main)
{
}

// déclare une fonction membre statique extern "C" ? < ===
static thread_function_t needle;

private:
threadable main;
};
};

// définit une fonction membre statique extern "C" ? < ===

template<typename threadable>
void* threader::threaded<threadable>::needle(void *eye)
{
std::auto_ptr<threaded> that(static_cast<threaded *>(eye));
return new return_type<threadable>::type(that->main());
}

Est-ce legal de cette façon ?


Non. (Mais je l'ai peut-être cru à une époque. Et si
threaded<>::needle utilisait bien les conventions d'appel C, ça
pourrait marcher.) Un « extern "C" » est ignoré sur une fonction
membre, même statique. (Et ne me démande pas pourquoi c'est
permis, si ça n'a aucun effet.)

En tout cas ça compile sans un warning avec VC 2005.


Ça ne le doit pas. La norme exige ici une erreur. Déjà l'ancien
compilateur de Zortech en donnait une, il y a plus de quinze
ans. (En revanche, c'était à une époque pré-norme, et peut-être
le Zortech prenait bien en compte l'« extern "C" ».

--
James Kanze



Avatar
Manuel Zaccaria
James Kanze a écrit:
Manuel Zaccaria wrote:

J'ai cru comprendre dans un autre post qu'on pouvait passer
par un typedef 'magique' si on avait vraiment besoin d'une
fonction membre à la fois «static» et «extern "c"».


Non. Rien qu'à partir du concepte, c'est impossible. Rien ne
dit que les deux langages passent leurs paramètres de la même
façon : le C++ pourrait les passer dans les registres, par
exemple, et le C sur la pile. (Si de telles différences me
semblent peu probables, j'ai déjà vu le cas sur Intel où la
responsibilité du nettoyage de la pile n'était pas la même.) Or,
si celui qui utilise le pointeur s'attend à une fonction C, il
va passer les paramètres comme le veut le C. Et ta fonction C++
va avoir du mal à s'y retrouver.


Certes! Tu as raison.
Je n'aurais pas dû retirer WINAPI dans le code que j'ai posté.
C'était plus du pseudo-code qu'autre chose. Il fallait lire ceci:

extern "C" typedef void* (WINAPI thread_function_t)(void*);

Ce qui spécifie la convention d'appel "pascal" sauf erreur.
C'est encore plus tordu donc et pas portable du tout (WIN32).

[...]

template<typename nullary_function>
struct return_type
{
typedef typename nullary_function::result_type type;
};
template<typename function_result_type>
struct return_type<function_result_type (*)()>
{
typedef function_result_type type;
};



[...]

Il y a d'autres solutions ; regarde, par exemple, comment fait
boost.threads. Sinon, la solution classique, c'est bien
d'utiliser une classe abstraite de base ; il y a bien une
conversion void* en AbstractThread* dans la fonction qu'appelle
le système, mais cette fonction est dans le sous-système des
threads. On assure donc une correction de type vis-à-vis des
utilisateurs.


La solution de Kevlin permet justement d'éviter de dériver d'une ABC.
Si tu ne l'a pas encore fait et si tu a le temps, jette juste un coup
d'oeil sur le 1er pdf que j'ai pointé (c'est très vite lu).

L'objet de type 'threadable' (le parametre de template) peut être
indifferement une fonction libre ou une classe avec un 'operator()'.


ex:

int any_nullary_fun()
{
return 42;
}

ou

class AnyJob // pas besoin de base
{
public:
typedef int result_type;

AnyJob(int n) : n(n) {}

int operator()()
{
return n*42;
}
private:
int n;
};

...

joiner<int> answer = thread(any_nullary_fun);

ou

joiner<int> answer = thread(AnyJob(24));

...

int result = answer();


static void *needle(void *eye)



[...]

Elle pourrait être une fonction libre templatée, sauf qu'une
fonction templatée ne peut pas être « extern "C" » non plus.
Mais...


Le C++ ne pourra pas ignorer les threads/callbacks indéfiniment...

Aussi, en passant, l'utilisation d'auto_ptr ici ne marche pas
avec tous les compilateurs ; le destructeur n'en serait pas
appelé par g++, par exemple, en cas de pthread_cancel.


Encore vrai mais windows ignore POSIX de ce coté, non ?
Pour la portabilité, boost est la voie à suivre de toute façon.

Je ne vois rien dans ce code qui exige que la fonction soit
membre ; l'utilisation d'une classe abstraite de base fonctionne
aussi bien.


Et sans base abstraite ? Qu'en penses-tu ?

titre d'exercice, J'ai implémenté «threader»
de cette façon après avoir lu un truc (de toi?) sur c.l.c++.m.


Je doute que ce soit de moi. Par hazard, je suis une des rares
(apparamment) personnes qui se sont réelement servies d'un
compilateur où ça faisait une différence réele ; je suis donc
assez sensibilisé sur le problème.


Sorry, j'ai confondu mais tu as participé au fil c'est sur :)

Non. (Mais je l'ai peut-être cru à une époque. Et si
threaded<>::needle utilisait bien les conventions d'appel C, ça
pourrait marcher.) Un « extern "C" » est ignoré sur une fonction
membre, même statique. (Et ne me démande pas pourquoi c'est
permis, si ça n'a aucun effet.)


Oui mais c'est le typedef (hors de la classe) qui a un
linkage «extern "C"», pas le membre statique needle.

Bah... en résumé cette fonction:

a un linkage C
a une convention d'appel pascal
est membre statique d'une class template

My head esplode!

-
Manuel Zaccaria


Avatar
none
merci tous.
Je sais que c'est plutôt une réponse chiante mais ... j'ai encore
beaucoup de choses à apprendre !
Avatar
kanze
Manuel Zaccaria wrote:
James Kanze a écrit:
Manuel Zaccaria wrote:

J'ai cru comprendre dans un autre post qu'on pouvait passer
par un typedef 'magique' si on avait vraiment besoin d'une
fonction membre à la fois «static» et «extern "c"».


Non. Rien qu'à partir du concepte, c'est impossible. Rien ne
dit que les deux langages passent leurs paramètres de la même
façon : le C++ pourrait les passer dans les registres, par
exemple, et le C sur la pile. (Si de telles différences me
semblent peu probables, j'ai déjà vu le cas sur Intel où la
responsibilité du nettoyage de la pile n'était pas la même.) Or,
si celui qui utilise le pointeur s'attend à une fonction C, il
va passer les paramètres comme le veut le C. Et ta fonction C++
va avoir du mal à s'y retrouver.


Certes! Tu as raison.
Je n'aurais pas dû retirer WINAPI dans le code que j'ai posté.
C'était plus du pseudo-code qu'autre chose. Il fallait lire ceci:

extern "C" typedef void* (WINAPI thread_function_t)(void*);

Ce qui spécifie la convention d'appel "pascal" sauf erreur.
C'est encore plus tordu donc et pas portable du tout (WIN32).


En effet. Mais de toute façon, il s'agissait de lancer un
thread, qui n'est pas portable par définition. Si l'API du
système a des exigeances particulières, il faut y adhérer.

Mais du coup, évidemment, ça en rend plus difficile la
discussion. Étant donné que WINAPI est une extension du langage,
rien n'empèche que Microsoft permet l'utilisation de cette
extension sur des fonctions membres statiques -- je n'en sais
rien, étant donné que les ressources à ma disposition c'est
d'une part la norme, et de l'autre, des compilateurs sous Linux
et Solaris. En revanche, si ça marche, je ne vois pas pourquoi
s'en servir, puisque le code n'est pas portable de toute façon.

[...]

template<typename nullary_function>
struct return_type
{
typedef typename nullary_function::result_type type;
};
template<typename function_result_type>
struct return_type<function_result_type (*)()>
{
typedef function_result_type type;
};



[...]

Il y a d'autres solutions ; regarde, par exemple, comment
fait boost.threads. Sinon, la solution classique, c'est bien
d'utiliser une classe abstraite de base ; il y a bien une
conversion void* en AbstractThread* dans la fonction
qu'appelle le système, mais cette fonction est dans le
sous-système des threads. On assure donc une correction de
type vis-à-vis des utilisateurs.


La solution de Kevlin permet justement d'éviter de dériver
d'une ABC.


Et qu'est-ce que ça lui apporte, de ne pas en dériver ?
Parfois, ça apporte la possibilité d'utiliser des classes
existantes, qui n'en dérive pas. Mais ici, c'est bien une classe
interne qui en dérivera. Alors, je ne vois pas ce que ça change,
en dériver ou non.

Si tu ne l'a pas encore fait et si tu a le temps, jette juste
un coup d'oeil sur le 1er pdf que j'ai pointé (c'est très vite
lu).


Je viens de le faire. Ils ne disent pas grand chose, d'après ce
que je peux voir ; Kelvin a écrit des choses beaucoup plus
détaillées pour le comité de normalisation. Aussi, les exemples
ne se laissent compiler nulle part ; je crois qu'il faut les
prendre plutôt comme pseudo-code. (Régard comment ils prenent
l'adresse de la fonction statique, par exemple. Ce n'est pas du
C++, et ça ne marche pas avec les compilateurs que j'utilise.)

L'objet de type 'threadable' (le parametre de template) peut
être indifferement une fonction libre ou une classe avec un
'operator()'.


Ça, j'ai compris. C'est aussi le cas pour les threads chez
Boost. Mais dans le cas de Kevlin, l'utilisation d'une classe de
base abstraite n'y change rien, puisque c'est la classe
templatée embriquée qui en dérivera, et non l'objet fonctionnel
du thread. (Dans ton exemple, la classe threader::threaded<>.)

ex:

int any_nullary_fun()
{
return 42;
}

ou

class AnyJob // pas besoin de base
{
public:
typedef int result_type;

AnyJob(int n) : n(n) {}

int operator()()
{
return n*42;
}
private:
int n;
};

...

joiner<int> answer = thread(any_nullary_fun);

ou

joiner<int> answer = thread(AnyJob(24));

...

int result = answer();

static void *needle(void *eye)




J'ai bien compris. Mais je ne vois pas où le fait que la classe
threader dérive d'une classe abstraite en changerait quoique ce
soit.

[...]

Elle pourrait être une fonction libre templatée, sauf qu'une
fonction templatée ne peut pas être « extern "C" » non plus.
Mais...


Le C++ ne pourra pas ignorer les threads/callbacks indéfiniment...


Elle ne le ferait pas. On parle sérieusement du threading pour
la prochaine édition de la norme. Mais ça ne changera rien dans
les principes en question ici : si l'API veut l'adresse d'une
fonction C comme callback, il faut lui donner l'adresse d'une
fonction qui se comporte exactement comme une fonction C à son
interface. Une fonction C++ ne fera pas l'affaire, non plus
qu'une fonction Java, ni une fonction Ada. C'est pourquoi la
plupart des langages modernes ont le concepte d'un appel
« étranger », l'« extern "langage" » du C++, n'en est qu'une
exemple. (Essaie de faire la même chose en Java, par exemple.
Là, il te faut du JNI, ce qui est autrement compliqué que
« extern "C" ».)

Aussi, en passant, l'utilisation d'auto_ptr ici ne marche pas
avec tous les compilateurs ; le destructeur n'en serait pas
appelé par g++, par exemple, en cas de pthread_cancel.


Encore vrai mais windows ignore POSIX de ce coté, non ?


Il doit bien y avoir quelque chose d'équivalent. Sinon, c'est
parfois un peu pénible : on n'ose pas vraiment bloquer le
thread sur une requête ; il faut plutôt mettre des hors-temps
assez courts, et faire du polling, ce qui utilise des ressources
machine pour rien.

Pour la portabilité, boost est la voie à suivre de toute façon.


Peut-être. C'est loin d'être certain que l'interface de Boost
soit celle adoptée. Elle a des faiblesses notoires.

Je ne vois rien dans ce code qui exige que la fonction soit
membre ; l'utilisation d'une classe abstraite de base
fonctionne aussi bien.


Et sans base abstraite ? Qu'en penses-tu ?


J'ai mes doutes, au moins avec de la métaprogrammation des
templates. (C'est trivial avec de la vraie métaprogrammation, en
dehors du C++.) Mais je ne vois pas de raison non plus à perdre
du temps pour chercher une telle solution, non plus.

Je ne sais pas comment Boost s'en sort. Il se sert bien d'un
objet de type boost::function0, qu'il copie. Alors, a priori, je
suppose qu'il y a une fonction virtuelle cachée dedans, parce
que le comportement de l'objet varie bien selon le constructeur
utilisé ; il doit y avoir quelque chose du genre de l'idiome
lettre/enveloppe (mais il y a tellement de macros dans
l'implémentation de boost::function que je n'arrive pas à
suivre).

titre d'exercice, J'ai implémenté «threader»
de cette façon après avoir lu un truc (de toi?) sur c.l.c++.m.


Je doute que ce soit de moi. Par hazard, je suis une des rares
(apparamment) personnes qui se sont réelement servies d'un
compilateur où ça faisait une différence réele ; je suis donc
assez sensibilisé sur le problème.


Sorry, j'ai confondu mais tu as participé au fil c'est sur :)

Non. (Mais je l'ai peut-être cru à une époque. Et si
threaded<>::needle utilisait bien les conventions d'appel C, ça
pourrait marcher.) Un « extern "C" » est ignoré sur une fonction
membre, même statique. (Et ne me démande pas pourquoi c'est
permis, si ça n'a aucun effet.)


Oui mais c'est le typedef (hors de la classe) qui a un
linkage «extern "C"», pas le membre statique needle.


Mais ça ne change rien. L'« extern "C" » est ignoré sur une
fonction membre, statique ou non.

Bah... en résumé cette fonction:

a un linkage C
a une convention d'appel pascal
est membre statique d'une class template


En ce qui concerne la convention d'appel pascal, c'est une
extension de Windows, dont je ne sais rien. En ce qui concerne
la norme, en revanche, il n'y a pas de fonction membre avec un
linkage "C". N'importe comment on s'y prend. Et à part
l'histoire de la convention d'appel Pascal, on a exactement le
même problème sous Unix.

--
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
Manuel Zaccaria
"kanze" a écrit:
Manuel Zaccaria wrote:

En effet. Mais de toute façon, il s'agissait de lancer un
thread, qui n'est pas portable par définition. Si l'API du
système a des exigeances particulières, il faut y adhérer.


Oui! Mais ça concerne également tous les callbacks disponible sur
une plateforme donnée. Pas seulement les threads. AMHA.

Mais du coup, évidemment, ça en rend plus difficile la
discussion. Étant donné que WINAPI est une extension du langage,
rien n'empèche que Microsoft permet l'utilisation de cette
extension sur des fonctions membres statiques -- je n'en sais


En ce qui concerne la convention d'appel (pas le linkage).
J'ai vérifié. C'est même permis sur les fonctions membres
non statiques selon la doc de Microsoft. Je ne vais pas exploiter
cette possibilité pour autant, même sous la torture.

et Solaris. En revanche, si ça marche, je ne vois pas pourquoi
s'en servir, puisque le code n'est pas portable de toute façon.


Tu voulais dire "je ne vois pas pourquoi ne pas s'en servir" ?

Si Microsoft documente qu'on peut se servir de fonctions membres
statiques pour les callbacks, ce serais dommage de ne pas en profiter
si le reste du programme n'est pas portable (ex: utilise DirectX) ?


interne qui en dérivera. Alors, je ne vois pas ce que ça change,
en dériver ou non.


Ca réduit juste un peu plus le couplage, non ? Ou pas du tout ?

Le C++ ne pourra pas ignorer les threads/callbacks indéfiniment...


Elle ne le ferait pas. On parle sérieusement du threading pour
la prochaine édition de la norme. Mais ça ne changera rien dans
les principes en question ici : si l'API veut l'adresse d'une
fonction C comme callback, il faut lui donner l'adresse d'une
fonction qui se comporte exactement comme une fonction C à son
interface. Une fonction C++ ne fera pas l'affaire, non plus


Mais comme ce callback est spécifique de la plateforme cible,
le programme n'est plus portable par définition...
Alors, qu'est-ce qui est acceptable finalement ?

qu'une fonction Java, ni une fonction Ada. C'est pourquoi la
plupart des langages modernes ont le concepte d'un appel
« étranger », l'« extern "langage" » du C++, n'en est qu'une
exemple. (Essaie de faire la même chose en Java, par exemple.
Là, il te faut du JNI, ce qui est autrement compliqué que
« extern "C" ».)


Je passe mon tour ;-)

Il doit bien y avoir quelque chose d'équivalent. Sinon, c'est
parfois un peu pénible : on n'ose pas vraiment bloquer le
thread sur une requête ; il faut plutôt mettre des hors-temps
assez courts, et faire du polling, ce qui utilise des ressources
machine pour rien.


TerminateThread(handle, exit_code);
...doit faire l'affaire, c'est pas du propre.

Pour la portabilité, boost est la voie à suivre de toute façon.


Peut-être. C'est loin d'être certain que l'interface de Boost
soit celle adoptée. Elle a des faiblesses notoires.


Il y a mieux ? je suis preneur.

Et sans base abstraite ? Qu'en penses-tu ?


J'ai mes doutes, au moins avec de la métaprogrammation des
templates. (C'est trivial avec de la vraie métaprogrammation, en
dehors du C++.) Mais je ne vois pas de raison non plus à perdre
du temps pour chercher une telle solution, non plus.


C'était juste un exercice de style, qui contient une erreur en
rapport avec le sujet de la discussion. Je ne discute pas de la
pertinence ou non de la solution de Kevlin.

Je ne sais pas comment Boost s'en sort. Il se sert bien d'un
objet de type boost::function0, qu'il copie. Alors, a priori, je
suppose qu'il y a une fonction virtuelle cachée dedans, parce
que le comportement de l'objet varie bien selon le constructeur
utilisé ; il doit y avoir quelque chose du genre de l'idiome
lettre/enveloppe (mais il y a tellement de macros dans
l'implémentation de boost::function que je n'arrive pas à
suivre).


LOL. Je vois ce que tu veux dire, je viens d'y jeter un oeil :-I

Oui mais c'est le typedef (hors de la classe) qui a un
linkage «extern "C"», pas le membre statique needle.


Mais ça ne change rien. L'« extern "C" » est ignoré sur une
fonction membre, statique ou non.


Eh bien, soit! Tans pis.

En ce qui concerne la convention d'appel pascal, c'est une
extension de Windows, dont je ne sais rien. En ce qui concerne


En fait je crois que c'est même pas du pascal car les paramètre
sont empilés de droite à gauche comme en C mais que c'est la
fonction appelée qui est responsable du dépilement au retour.

la norme, en revanche, il n'y a pas de fonction membre avec un
linkage "C". N'importe comment on s'y prend. Et à part


Oui je sais... le typedef était tordu, pouvant laisser croire
le contraire ou en tout cas créer la confusion dans ma tête.

l'histoire de la convention d'appel Pascal, on a exactement le
même problème sous Unix.


En conclusion et si j'ai bien saisi, le mieux que l'on puisse faire,
c'est une fonction libre (extern "C") et une classe de base abstraite.


-
Manuel Zaccaria


Avatar
kanze
Manuel Zaccaria wrote:
"kanze" a écrit:
Manuel Zaccaria wrote:

En effet. Mais de toute façon, il s'agissait de lancer un
thread, qui n'est pas portable par définition. Si l'API du
système a des exigeances particulières, il faut y adhérer.


Oui! Mais ça concerne également tous les callbacks disponible
sur une plateforme donnée. Pas seulement les threads. AMHA.


Tout à fait. Et même des callbacks des bibliothèques tièrces,
genre wxWidgets. Il faut toujours que le callback suit les
conventions préscrites par le sous-système qui va l'appeler.

Mais du coup, évidemment, ça en rend plus difficile la
discussion. Étant donné que WINAPI est une extension du langage,
rien n'empèche que Microsoft permet l'utilisation de cette
extension sur des fonctions membres statiques -- je n'en sais


En ce qui concerne la convention d'appel (pas le linkage).
J'ai vérifié. C'est même permis sur les fonctions membres non
statiques selon la doc de Microsoft. Je ne vais pas exploiter
cette possibilité pour autant, même sous la torture.


Permis, c'est une chose -- « extern "C" » est bien permis sur
un fonction membre, mais par définition n'y a pas effet.

A priori, il n'y a aucun rapport essentiel entre les conventions
d'appel (c-à-d des choses comme l'ordre des paramètres, sur la
pile ou dans des registres, etc.) et que la fonction soit membre
ou non. Seulement, pour des raisons évidentes, on veut que les
conventions se retrouvent dans la décoration de la fonction, de
façon à ne pas pouvoir appeler une fonction avec de mauvaises
conventions. Et dans le cas d'« extern "C" », il faut
également que la décoration soit la même qu'en C, c-à-d
typiquement soit rien, soit juste un _ avant le nom, mais sans
le moindre indication en ce qui concerne si la fonction est
membre, etc. C'est sans doute pour cette raison que le C++ ne
permet pas de linkage C sur des fonctions membres, même
statiques.

et Solaris. En revanche, si ça marche, je ne vois pas
pourquoi s'en servir, puisque le code n'est pas portable de
toute façon.


Tu voulais dire "je ne vois pas pourquoi ne pas s'en servir" ?


Oui.

Si Microsoft documente qu'on peut se servir de fonctions
membres statiques pour les callbacks, ce serais dommage de ne
pas en profiter si le reste du programme n'est pas portable
(ex: utilise DirectX) ?


Si c'est pour un callback d'une interface Microsoft, je dirais
pourquoi pas s'en servir ? Le code ne marchera de toute façon
pas sous Linux.

Quand il s'agit d'une fonctionnalité généralement disponible,
même si l'interface est propre à Microsoft, on pourrait se poser
la question. J'imagine, par exemple, que même si le nom de la
fonction dans l'API n'est pas pthread_create, il y a pas mal de
ressemblances dans sa fonctionnalité. Je dois bien isoler
l'appel, de façon à appeler une fonction différente, avec
éventuellement des paramètres différents, dans un ordre
différent. Mais de là à utiliser une fonction membre statique
dans un cas, et une fonction libre dans l'autre...

interne qui en dérivera. Alors, je ne vois pas ce que ça
change, en dériver ou non.


Ca réduit juste un peu plus le couplage, non ? Ou pas du tout ?


Pas à ce que je vois. (Évidemment, la fonction libre se trouvera
dans l'espace référentiel anonyme de la module qui implémente
tout ça.)

Le C++ ne pourra pas ignorer les threads/callbacks
indéfiniment...


Elle ne le ferait pas. On parle sérieusement du threading
pour la prochaine édition de la norme. Mais ça ne changera
rien dans les principes en question ici : si l'API veut
l'adresse d'une fonction C comme callback, il faut lui
donner l'adresse d'une fonction qui se comporte exactement
comme une fonction C à son interface. Une fonction C++ ne
fera pas l'affaire, non plus


Mais comme ce callback est spécifique de la plateforme cible,
le programme n'est plus portable par définition... Alors,
qu'est-ce qui est acceptable finalement ?


Ça dépend de l'interface. Pour pthread_create, il faut bien que
la fonction soit « extern "C" ». Si tu essaies d'écrire
quelque chose qui pourrait servir sur les deux plateformes, et
que tu veux limiter les dépendances autant que se peut, c'est
probablement préférable d'utiliser une fonction libre sous
Windows aussi. Pour d'autres interfaces, en revanche, qui n'ont
pas d'équivalent sous Unix, ou où l'équivalent sous Unix part
d'une autre philosophie, qui impose une autre façon d'organiser
le code, je ne vois aucune raison particulière d'éviter les
extensions Microsoft (de même que j'utilise regulièrement des
extensions Posix dans mon propre code).

[...]
Il doit bien y avoir quelque chose d'équivalent. Sinon,
c'est parfois un peu pénible : on n'ose pas vraiment bloquer
le thread sur une requête ; il faut plutôt mettre des
hors-temps assez courts, et faire du polling, ce qui utilise
des ressources machine pour rien.


TerminateThread(handle, exit_code);
...doit faire l'affaire, c'est pas du propre.


Attention. Le nom de pthread_cancel est un peu trompeur, parce
qu'il n'impose pas l'arrêt de l'autre thread. Tout ce qu'il
fait, c'est de signaler que l'arrêt est souhaité. Par rapport à
une simple variable booléene, il y a comme différences : quand
le thread ciblé teste la variable, si elle est vrai, il se passe
à peu près l'équivalent d'une exception, et qu'il y a un test
implicit dans un certain nombre d'appels système autrement
bloquants, qui donc se débloquent (mais un thread peut dire ou
qu'il ne veut pas être cancelé du tout, ou qu'on peut le faire à
n'importe quel instant). Aussi, ce déblocage et le
positionnement de la variable sont atomiques, de façon à ce
qu'il ne peut pas y avoir des conditions de race.

Il y a certainement d'autres façons d'y arriver, mais pour faire
un shutdown propre, il faut bien notifier tous les threads qu'on
veut s'arrêter, en les débloquant le cas échéant. Sinon, on est
amené à mettre des hors-temps assez court sur tout appel
potentiellement bloquant, et à faire du polling sur le drappeau
d'arrêt. Solution qui marche pas mal dans beaucoup
d'applications, mais qui est loin d'être satisfaisant en
général.

Pour la portabilité, boost est la voie à suivre de toute
façon.


Peut-être. C'est loin d'être certain que l'interface de Boost
soit celle adoptée. Elle a des faiblesses notoires.


Il y a mieux ? je suis preneur.


Mieux, je ne sais pas. Mais celle de Boost a quelques problèmes
sérieux, comme le fait qu'une exception non-catchée puisse
rendre un thread détaché. Elle ne permet pas non plus la
propagation d'une exception à travers un join.

[...]
En ce qui concerne la convention d'appel pascal, c'est une
extension de Windows, dont je ne sais rien. En ce qui concerne


En fait je crois que c'est même pas du pascal car les paramètre
sont empilés de droite à gauche comme en C mais que c'est la
fonction appelée qui est responsable du dépilement au retour.


Ce qui doit être le cas normal pour toute fonction C++
non-varargs, non ? (C'était le cas avec le compilateur Zortech,
dans le temps. Et avec le compilateur Intel 80386 dont je me
suis servi pour les systèmes embarqués.)

[...]
En conclusion et si j'ai bien saisi, le mieux que l'on puisse
faire, c'est une fonction libre (extern "C") et une classe de
base abstraite.


Quand il s'agit d'une fonctionnalité qu'on risque de vouloir sur
d'autres systèmes, en tout cas. À vrai dire, j'aurais tendance à
l'universaliser, simplement pour utiliser le même idiome dans
tous les cas.

--
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
Manuel Zaccaria
"kanze" a écrit:
Manuel Zaccaria wrote:
"kanze" a écrit:
Manuel Zaccaria wrote:


En ce qui concerne la convention d'appel (pas le linkage).
J'ai vérifié. C'est même permis sur les fonctions membres non
statiques selon la doc de Microsoft. Je ne vais pas exploiter
cette possibilité pour autant, même sous la torture.


Permis, c'est une chose -- « extern "C" » est bien permis sur
un fonction membre, mais par définition n'y a pas effet.


C'est clair. Car décoré de toute manière.

A priori, il n'y a aucun rapport essentiel entre les conventions
d'appel (c-à-d des choses comme l'ordre des paramètres, sur la
pile ou dans des registres, etc.) et que la fonction soit membre
ou non.


Oui. Ce sont deux concepts orthogonaux. Je parlais seulement
de la convention d'appel, qu'on peut préciser sur une fonction
membre, __cdecl (défaut), __stdcall (WINAPI) et __fastcall.

Seulement, pour des raisons évidentes, on veut que les
conventions se retrouvent dans la décoration de la fonction, de
façon à ne pas pouvoir appeler une fonction avec de mauvaises
conventions. Et dans le cas d'« extern "C" », il faut
également que la décoration soit la même qu'en C, c-à-d
typiquement soit rien, soit juste un _ avant le nom, mais sans
le moindre indication en ce qui concerne si la fonction est
membre, etc. C'est sans doute pour cette raison que le C++ ne
permet pas de linkage C sur des fonctions membres, même
statiques.


Oui.

Quand il s'agit d'une fonctionnalité généralement disponible,
même si l'interface est propre à Microsoft, on pourrait se poser
la question. J'imagine, par exemple, que même si le nom de la
fonction dans l'API n'est pas pthread_create, il y a pas mal de
ressemblances dans sa fonctionnalité. Je dois bien isoler
l'appel, de façon à appeler une fonction différente, avec
éventuellement des paramètres différents, dans un ordre
différent. Mais de là à utiliser une fonction membre statique
dans un cas, et une fonction libre dans l'autre...


Autant adopter le même idiome partout, avec éventuellement une
façade pour isoler le sous-système du programme.

Mais comme ce callback est spécifique de la plateforme cible,
le programme n'est plus portable par définition... Alors,
qu'est-ce qui est acceptable finalement ?


Ça dépend de l'interface. Pour pthread_create, il faut bien que
la fonction soit « extern "C" ». Si tu essaies d'écrire
quelque chose qui pourrait servir sur les deux plateformes, et
que tu veux limiter les dépendances autant que se peut, c'est
probablement préférable d'utiliser une fonction libre sous
Windows aussi. Pour d'autres interfaces, en revanche, qui n'ont
pas d'équivalent sous Unix, ou où l'équivalent sous Unix part
d'une autre philosophie, qui impose une autre façon d'organiser
le code, je ne vois aucune raison particulière d'éviter les
extensions Microsoft (de même que j'utilise regulièrement des
extensions Posix dans mon propre code).


J'ai une réaction épidermique en ce qui concerne la politique/la
façon de faire de Microsoft. Pour résumer: 'Enough is enough'.
Je n'utiliserai plus jamais les MFC par exemple... quelle plaie.

TerminateThread(handle, exit_code);
...doit faire l'affaire, c'est pas du propre.


Attention. Le nom de pthread_cancel est un peu trompeur, parce
qu'il n'impose pas l'arrêt de l'autre thread. Tout ce qu'il


Tu constates que je n'ai "pas beaucoup d'expérience" avec UNIX ;-)

fait, c'est de signaler que l'arrêt est souhaité. Par rapport à
une simple variable booléene, il y a comme différences : quand
le thread ciblé teste la variable, si elle est vrai, il se passe
à peu près l'équivalent d'une exception, et qu'il y a un test
implicit dans un certain nombre d'appels système autrement
bloquants, qui donc se débloquent (mais un thread peut dire ou
qu'il ne veut pas être cancelé du tout, ou qu'on peut le faire à
n'importe quel instant). Aussi, ce déblocage et le
positionnement de la variable sont atomiques, de façon à ce
qu'il ne peut pas y avoir des conditions de race.


Alors je ne crois pas qu'il y a un équivalent sou Windows. Pour ça,
j'utilise la fonction bloquante 'WaitForMultipleObjects()'.

Peut-être. C'est loin d'être certain que l'interface de Boost
soit celle adoptée. Elle a des faiblesses notoires.


Il y a mieux ? je suis preneur.


Mieux, je ne sais pas. Mais celle de Boost a quelques problèmes
sérieux, comme le fait qu'une exception non-catchée puisse
rendre un thread détaché. Elle ne permet pas non plus la
propagation d'une exception à travers un join.


Le mécanisme RTTI est malheureusement insuffisant pour propager
une exception d'un thread à un autre (j'ai essayé... j'ai peut-être
raté un truc, BTW).
Si seulement le C++ supportait l'introspection, ça résoudrait
bien des problèmes, surtout pour la sérialisation.
On réinvente sans cesse la roue (Factories), ça devient pénible.

En fait je crois que c'est même pas du pascal car les paramètre
sont empilés de droite à gauche comme en C mais que c'est la
fonction appelée qui est responsable du dépilement au retour.


Ce qui doit être le cas normal pour toute fonction C++
non-varargs, non ? (C'était le cas avec le compilateur Zortech,
dans le temps. Et avec le compilateur Intel 80386 dont je me
suis servi pour les systèmes embarqués.)


Dans l'idéal je suis d'accord. Ce n'est pas le cas de VC, qui utilise
la convention __cdecl (compatible varargs) par défaut partout.



-
Manuel Zaccaria



1 2