Quand on a un seul paramètre à renvoyer, à la lecture du code, utiliser une valeur de retour me semble plus simple.
A cause d'une copie inutile de recup vers all. En le déplaçant les timings devraient être beaucoup plus proches.:
Pour ce qui est de la question initiale, je pense que la majorité des codeurs C++ utiliseraient la 1ère méthode, celle avec la référence qui me semble être un bon compromis perf/facilité d'utilisation/lisibilité et qui permet surtout de renvoyer un code d'erreur ou un booléen si le besoin se fait sentir.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 std::vector<std::string> recup(getList()); std::move(recup.begin(), recup.end(), std::back_inserter(all));
Ne t'attardes peut être pas trop sur la manière dont j'ai utilisé FileHolder, qui n'était qu'un exemple, d'ailleurs sans doute mal choisi au demeurant (surement mal choisi, à lire ta réaction )
Ce qui importe, c'est surtout d'arriver à utiliser une abstraction capable de représenter "n'importe quel système de fichiers".
Qu'il s'agisse d'un serveur, d'une tablette, d'un GSM, d'un GPS ou du troisième sous répertoire du disque E de ton pc, c'est le même combat et tu devrais pouvoir utiliser la même abstraction : tu as un dossier, composé sans doute de répertoires et de fichiers, dont tu peux récupérer le nom et d'autres informations (comme la date et l'heure de la création, la date et l'heure de la dernière modification, les droits pour différents types de personnes, ...) qui te permet de travailler "de la même manière, quelles que soient les conditions de travail".
Je penses, par exemple, à quelque chose comme boost.filesystem qui présente ce genre d'abstraction et qui fonctionne avec n'importe quel système de fichier concret.
La manière d'utiliser une telle abstraction dépend évidemment très largement de l'interface publique quelle présente, et c'est à toi d'en tenir compte
A méditer: La solution la plus simple est toujours la moins compliquée
Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
Compiler Gcc sous windows avec MinGW
Coder efficacement en C++ : dans les bacs le 17 février 2014
mon tout nouveau blog
Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.
Bien vu,
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 getAll: 13.3958s getAllMove: 15.4859s
A méditer: La solution la plus simple est toujours la moins compliquée
Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
Compiler Gcc sous windows avec MinGW
Coder efficacement en C++ : dans les bacs le 17 février 2014
mon tout nouveau blog
+1 avec la réponse de Loic:
Personnellement j'utilise quasiment toujours cette version puisque en tant qu'utilisateur de la classe j'attends avec un nom comme getPhotoList de pouvoir écrire un truc comme:De mon point de vue, et à moins d'en savoir plus sur la manière d'utiliser la fonction (et de se rendre compte que le pattern d'utilisation va à l'encontre de l'interface), je partirais en C++11 vers le plus simple : Une fonction qui retourne un vector.
Alors que la version en passant la collection en paramètre laisse plein d'inconnues: est-ce que le conteneur doit avoir préalloué la mémoire pour les futurs éléments?, est-ce qu'ils seront ajoutés à la fin?, est-ce que le conteneur sera réinitialisé?, ... Tant de question sans réponse (à moins d'aller lire la doc, ce qui est une bonne pratique, mais je préfère une version plus "intuitive" qui évite les quiproquo)
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 for(auto const& photo : getPhotoList()) { //traitement sur la photo de la collection }
PS: en pratique j'utilise une version renvoyant un range avec un typedef sur le range
Voilà qui est intéressant, Andrei Alexandrescu vient tout juste de faire une présentation à la conf GoingNative (qui est live en ce moment) et tout une section de sa présentation était dédiée au fait que void GetPhotoList (const std::string& device_name, std::vector<std::string>& picture_list); est selon lui une bien meilleure interface que std::vector<std::string> GetPhotoList (const std::string& device_name);
Les avantages selon lui :
1) Plus composable
2) plus efficace
Précision importante, il part du principe que la forme avec la référence ajoute simplement à la fin du vecteur (faire des push_back en gros) plutôt que de réinitialiser et ajouter.
Voici un des exemples qu'il donne pour montrer la supériorité de l'API par référence (il n'en parle qu'à l'oral, sans code, je transcris ce que j'en ai compris).
L'api par référence permet de faire :
alors que l'api avec la valeur par retour oblige à faire :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 std::vector<std::string> v; while(some_condition) { v.clear(); AppendPhotoList(somedir, v); // je l'appelle AppendPhotoList pour distinguer avec celle qui retourne par valeur DoSomething(v); }
Et dans ce cas naturellement la forme n°1 est bien plus efficace car dans la forme 2 la fonction GetPhotoList va recréer complètement un vecteur à chaque appel, donc à chaque tour de boucle le programme désallouera et reéallouera de la mémoire alors que le vecteur du code 1 finira par atteindre une capacité maximale et ne réallouera plus du tout.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 while(some_condition) { DoSomething(GetPhotoList(somedir)); }
Je pense que dans ton premier bout de code, la bonne expression est :
Code : Sélectionner tout - Visualiser dans une fenêtre à part DoSomething(v);
Oui, merci, je corrige.
Intéressant.
En y réfléchissant, et en généralisant, je code bien plus souvent la première forme que la deuxième.
Même si souvent j'hésite parce que c'est plus "lourd" (à taper et à relire).
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8 //often recommended MyClass oi; while(some_condition) { //oi.init() or oi.clear(); whatever you need... GetByRef(someparam, oi); DoSomething(oi); }D'ailleurs parfois y'a pas le choix.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 //less often while(some_condition) { MyClass oi=GetByVal(someparam); DoSomething(oi); }
Il m'est arrivé alors d'utiliser des std::pair<> ou des tableaux de variant (des tuples, etc) comme valeur de retour.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 MyClass1 o1; MyClass2 o2; while(some_condition) { GetByRef(someparam, o1, o2); DoSomething(o1, o2); }
Oui... et puis non finalement.
A la longue le retour par valeur des objets "complexes" est finalement pénible (je ne me suis toujours pas fait au retour de std::map<>.insert() par exemple).
On a inventé les références, c'est pour s'en servir. Même si c'est plus ambigue, même s'il faut bien lire et comprendre la doc, même si c'est laid.
Mais, j'y pense...
J'aimerais pouvoir faire quelque chose comme ceci:
Je viens d'inventer l'opérateur ..
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 class MyClass1 { int col; }; class MyClass2 { int row; }; main() { std::pair<MyClass1, MyClass2> p=DoSometing(); //Calc(p.first.col, p.second.row); // lourdingue... Calc(p..col, p..row);//accept this shorter syntax if there is no ambiguity }
Il permettrait de désigner un membre ou un sous-membre implicitement, pour autant qu'il n'y ai pas d'amguité.
Vous en pensez quoi ?
Perso je me prends surtout à rêver d'une syntaxe comme en go qui autorise les valeurs de retour multiples. Quelque chose comme ça ...
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 DoSomething(void) -> MyClass1, MyClass2, bool { MyClass1 m1, m2; bool err = false; // code... return m1, m2, err; } main() { MyClass1 m1, MyClass2 m2, bool err = DoSomething(); if(err ) Calc(m1, m2); // // Note: En go on n'est pas obligé de définir tout sur la même ligne on peut // aussi faire : // MyClass1 m1; MyClass2 m2; // ... // utilisation de m1 et m2 pour autre chose // ... // m1, m2, bool err = DoSomething();
Les tuples sont tes amis (mais les tuples ont un gros défaut : l’absence de nommage de leurs composants, qui font que ça manque en général de sens. Les valeurs de retour ont le même problème, cela dit).
Wah, Alexandrescu est d'accord avec moi! C'est la consécration, je vais le mettre sur mon cv!
Bon par contre, on est d'accord sur la signature à utiliser, mais c'est pas pour les mêmes raisons
edit: j'en profite pour faire une remarque sur l'argument de JolyLoic concernant le fait qu'une fonction qui retourne un conteneur permet de chainer des appels. Moi je n'aime pas chainer des appels (c'est une des raisons qui font que je n'utilise plus systématiquement la STL). Je préfère un peu plus de lignes de code, mais des lignes plus simples, plus atomiques, sémantiquement parlant. Mais c'est une pratique, une préférence, je ne sais pas si c'est mieux ou moins bien.
« L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
Spinoza — Éthique III, Proposition VII
Il est presque d'accord avec toi Car Alexandrescu ne ferait jamais ça :
Vu qu'avec un swap trick tu forces la désallocation de la mémoire du vecteur passé en référence, donc tu perds la moitié de l'intérêt à utiliser cette signature (pas d'allocation inutile si le vecteur a déjà une capacité suffisante)
+1. Je n'aime pas chainer les appels non plus pour la simple raison que ça donne du code complètement indébuggable vu qu'on ne peut pas mettre de breakpoint pour vérifier étape par étape ce que renvoie chaque fonction.
Voici un exemple dans un contexte de serialisation:
-paramètre par référence pour sequence_in(), sequence_out(), ascending(), descending() (à cause des surcharges);
-valeur retour pour permettre le chainage d'appel avec asc(), desc(), operator,() (bien pratique et très visuel dans ce cas-ci).
inputoutput
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 //input template<typename Str> class sequencer_input { public: sequencer_input(Str const & stream); ... template<typename T> sequencer_input & operator,(T & v) { sequence_in(*this, v); return *this; } }; template<typename T, typename Str> bool run_sequencer_input(T & t, Str const & s) { ... sequencer_input<Str> ts(s); ... t.sequence(ts); ... return success; } template<typename Str, typename T> void sequence_in(sequencer_input<Str> & ts, T & t) { t.sequence(ts); } template<typename Str> void sequence_in(sequencer_input<Str> & ts, int & i) { ... } template<typename Str> void sequence_in(sequencer_input<Str> & ts, unsigned & u) { ... } template<typename Str> void sequence_in(sequencer_input<Str> & ts, std::string & s) { ... }order
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 //output template<typename Str> class sequencer_output { public: sequencer_output(Str & stream); ... template<typename T> sequencer_output & operator,(T const & v) { sequence_out(*this, v); return *this; } }; template<typename T, typename Str> bool run_sequencer_output(T const & t, Str & s) { ... sequencer_output<Str> ts(s); ... const_cast<T &>(t).sequence(ts); ... return success; } template<typename Str, typename T> void sequence_out(sequencer_output<Str> & ts, T const & t) { const_cast<T &>(t).sequence(ts); } template<typename Str> void sequence_out(sequencer_output<Str> & ts, int i) { ... } template<typename Str> void sequence_out(sequencer_output<Str> & ts, unsigned u) { ... } template<typename Str> void sequence_out(sequencer_output<Str> & ts, std::string const & s) { ... }Example d'utilisation, avec les méthodes descriptives MyRecord::sequence() et MyRecord::order_by().
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62 //order class sequencer_order { ... template<typename T> sequencer_order & asc(T v) { ascending(*this, v); return *this; } template<typename T> sequencer_order & desc(T v) { descending(*this, v); return *this; } }; inline void ascending(sequencer_order & sk, int i) { ... } inline void descending(sequencer_order & sk, int i) { ... } inline void ascending(sequencer_order & sk, unsigned u) { ... } inline void descending(sequencer_order & sk, unsigned u) { ... } inline void ascending(sequencer_order & sk, std::string const & s) { ... } inline void descending(sequencer_order & sk, std::string const & s) { ... } template<typename T> struct sequencer_order_pred { mutable sequencer_order sk1, sk2; void (T::*mf_)(sequencer_order &) const; // the member function pointer explicit sequencer_order_pred(void (T::*mf)(sequencer_order &) const): mf_(mf) {} bool operator()(T const & v1, T const & v2) const { sk1.clear(); (v1.*mf_)(sk1); sk2.clear(); (v2.*mf_)(sk2); return ::memcmp(sk1.buffer(), sk2.buffer(), std::min(sk1.size(), sk2.size())) < 0; } };
Bon, voilà, vite fait, comme ça, je ne sais pas ce que ça vaut
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 struct MyRecord { int m_age; std::string m_nom, m_prenom; template<typename F> void sequence(F & f) { f, m_age, m_nom, m_prenom; } void order_by(sequencer_order & sk) const { sk.desc(m_age).asc(m_nom).asc(m_prenom); } }; main() { //read std::string si; sequencer_input<std::string> stri(si); std::vector<MyRecord> v; ... MyRecord r; while (cond) { if (run_sequencer_input(r, stri)) { v.push_back(r); ... } } ... //sort std::sort(v.begin(), v.end(), sequencer_order_pred<MyRecord>(&MyRecord::order_by)); ... //write std::string so; sequencer_output<std::string> stro(so); ... foreach (MyRecord r in v) { if (!run_sequencer_output(r, stro)) { ... } } }
Enfin, juste pour dire que les références sont privilégiées, c'est utile pour surcharger ou ajouter des indirections lorsque nécessaire.
Exception faite lorsque la valeur retour est *this, j'adore chainer dans ce cas
Vous avez un bloqueur de publicités installé.
Le Club Developpez.com n'affiche que des publicités IT, discrètes et non intrusives.
Afin que nous puissions continuer à vous fournir gratuitement du contenu de qualité, merci de nous soutenir en désactivant votre bloqueur de publicités sur Developpez.com.
Partager