J'ai une classe appelée Writer
qui a une fonction writeVector
comme ceci:
void Drawer::writeVector(vector<T> vec, bool index=true)
{
for (unsigned int i = 0; i < vec.size(); i++) {
if (index) {
cout << i << "\t";
}
cout << vec[i] << "\n";
}
}
J'essaie de ne pas créer de code en double tout en m'inquiétant de la performance . Dans la fonction, je vérifie if (index)
à chaque tour de ma boucle for
, même si le résultat est toujours identique. Ceci est contre "se soucier de la performance".
Je pourrais facilement éviter cela en plaçant le contrôle en dehors de ma for
- loop . Cependant, je vais avoir beaucoup de code en double:
void Drawer::writeVector(...)
{
if (index) {
for (...) {
cout << i << "\t" << vec[i] << "\n";
}
}
else {
for (...) {
cout << vec[i] << "\n";
}
}
}
Il s’agit donc de deux "mauvaises" solutions pour moi ... Je pense à deux fonctions privées, l’une sortant de l’index, puis appelant l´autre ...__, l´autre excluant seulement la valeur .. Cependant, je ne sais pas comment l'utiliser avec mon programme, il me faudrait quand même le chèque if
pour savoir lequel appeler ...
Selon le problème, le polymorphisme semble être une solution correcte… .. Mais je ne vois pas comment l'utiliser ici… .. Quel serait le moyen privilégié de résoudre ce type de problème?
Ce n'est pas un vrai programme, Je voudrais juste savoir comment résoudre ce genre de problème.
Passe dans le corps de la boucle en tant que foncteur. Il est en ligne au moment de la compilation, pas de pénalité de performance.
L'idée de transmettre ce qui varie est omniprésente dans la bibliothèque standard C++. C'est ce qu'on appelle le modèle de stratégie.
Si vous êtes autorisé à utiliser C++ 11, vous pouvez faire quelque chose comme ceci:
#include <iostream>
#include <set>
#include <vector>
template <typename Container, typename Functor, typename Index = std::size_t>
void for_each_indexed(const Container& c, Functor f, Index index = 0) {
for (const auto& e : c)
f(index++, e);
}
int main() {
using namespace std;
set<char> s{'b', 'a', 'c'};
// indices starting at 1 instead of 0
for_each_indexed(s, [](size_t i, char e) { cout<<i<<'\t'<<e<<'\n'; }, 1u);
cout << "-----" << endl;
vector<int> v{77, 88, 99};
// without index
for_each_indexed(v, [](size_t , int e) { cout<<e<<'\n'; });
}
Ce code n'est pas parfait mais vous en avez l'idée.
Dans l'ancien C++ 98, cela ressemble à ceci:
#include <iostream>
#include <vector>
using namespace std;
struct with_index {
void operator()(ostream& out, vector<int>::size_type i, int e) {
out << i << '\t' << e << '\n';
}
};
struct without_index {
void operator()(ostream& out, vector<int>::size_type i, int e) {
out << e << '\n';
}
};
template <typename Func>
void writeVector(const vector<int>& v, Func f) {
for (vector<int>::size_type i=0; i<v.size(); ++i) {
f(cout, i, v[i]);
}
}
int main() {
vector<int> v;
v.Push_back(77);
v.Push_back(88);
v.Push_back(99);
writeVector(v, with_index());
cout << "-----" << endl;
writeVector(v, without_index());
return 0;
}
Encore une fois, le code est loin d’être parfait mais il vous en donne l’idée.
Dans la fonction, je vérifie si (index) chaque tour de ma boucle for, même si le résultat est toujours le même. Ceci est contre "se soucier de la performance".
Si c'est bien le cas, le prédicteur de branche n'aura aucun problème pour prédire le résultat (constant). En tant que tel, cela ne provoquera qu'une légère surcharge pour les erreurs de prédiction des premières itérations. Rien à craindre en termes de performance
Dans ce cas, je préconise de garder le test dans la boucle pour plus de clarté.
Pour développer la réponse de ALi, qui est parfaitement correcte mais qui duplique encore du code (une partie du corps de la boucle, cela est malheureusement difficilement évitable lors de l'utilisation du modèle de stratégie) ...
Certes, dans ce cas particulier, la duplication de code n’est pas grande chose, mais il existe un moyen de la réduire encore plus, ce qui est pratique si le corps de la fonction est plus gros que quelques instructions .
La clé consiste à utiliser la capacité du compilateur à exécuter le pliage constant/l'élimination du code mort . Nous pouvons le faire en mappant manuellement la valeur d'exécution de index
à une valeur au moment de la compilation (facile à faire lorsque le nombre de cas est limité - deux dans ce cas) et en utilisant un argument de modèle non typé connu à temps de compilation:
template<bool index = true>
// ^^^^^^ note: the default value is now part of the template version
// see below to understand why
void writeVector(const vector<int>& vec) {
for (size_t i = 0; i < vec.size(); ++i) {
if (index) { // compile-time constant: this test will always be eliminated
cout << i << "\t"; // this will only be kept if "index" is true
}
cout << vec[i] << "\n";
}
}
void writeVector(const vector<int>& vec, bool index)
// ^^^^^ note: no more default value, otherwise
// it would clash with the template overload
{
if (index) // runtime decision
writeVector<true>(vec);
// ^^^^ map it to a compile-time constant
else
writeVector<false>(vec);
}
De cette façon, nous obtenons un code compilé qui équivaut à votre deuxième exemple de code (if
extérieur/intérieur for
), mais sans dupliquer le code nous-mêmes. Nous pouvons maintenant rendre la version de modèle de writeVector
aussi compliquée que nous le souhaitons, il y aura toujours un seul élément de code à gérer.
Notez comment la version du modèle (qui prend une constante à la compilation sous la forme d'un argument de modèle non-type) et la version non-modèle (qui prend une variable d'exécution en tant qu'argument de fonction) sont surchargées. Cela vous permet de choisir la version la plus pertinente en fonction de vos besoins, avec une syntaxe assez similaire et facile à retenir dans les deux cas:
writeVector<true>(vec); // you already know at compile-time which version you want
// no need to go through the non-template runtime dispatching
writeVector(vec, index); // you don't know at compile-time what "index" will be
// so you have to use the non-template runtime dispatching
writeVector(vec); // you can even use your previous syntax using a default argument
// it will call the template overload directly
Dans la plupart des cas, votre code est déjà bon pour la performance et la lisibilité. Un bon compilateur est capable de détecter les invariants de boucle et de procéder aux optimisations appropriées. Prenons l'exemple suivant, très proche de votre code:
#include <cstdio>
#include <iterator>
void write_vector(int* begin, int* end, bool print_index = false) {
unsigned index = 0;
for(int* it = begin; it != end; ++it) {
if (print_index) {
std::printf("%d: %d\n", index, *it);
} else {
std::printf("%d\n", *it);
}
++index;
}
}
int my_vector[] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
};
int main(int argc, char** argv) {
write_vector(std::begin(my_vector), std::end(my_vector));
}
J'utilise la ligne de commande suivante pour le compiler:
g++ --version
g++ (GCC) 4.9.1
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
g++ -O3 -std=c++11 main.cpp
Ensuite, vidons Assembly:
objdump -d a.out | c++filt > main.s
Le résultat Assembly de write_vector
est:
00000000004005c0 <write_vector(int*, int*, bool)>:
4005c0: 48 39 f7 cmp %rsi,%rdi
4005c3: 41 54 Push %r12
4005c5: 49 89 f4 mov %rsi,%r12
4005c8: 55 Push %rbp
4005c9: 53 Push %rbx
4005ca: 48 89 fb mov %rdi,%rbx
4005cd: 74 25 je 4005f4 <write_vector(int*, int*, bool)+0x34>
4005cf: 84 d2 test %dl,%dl
4005d1: 74 2d je 400600 <write_vector(int*, int*, bool)+0x40>
4005d3: 31 ed xor %ebp,%ebp
4005d5: 0f 1f 00 nopl (%rax)
4005d8: 8b 13 mov (%rbx),%edx
4005da: 89 ee mov %ebp,%esi
4005dc: 31 c0 xor %eax,%eax
4005de: bf a4 06 40 00 mov $0x4006a4,%edi
4005e3: 48 83 c3 04 add $0x4,%rbx
4005e7: 83 c5 01 add $0x1,%ebp
4005ea: e8 81 fe ff ff callq 400470 <printf@plt>
4005ef: 49 39 dc cmp %rbx,%r12
4005f2: 75 e4 jne 4005d8 <write_vector(int*, int*, bool)+0x18>
4005f4: 5b pop %rbx
4005f5: 5d pop %rbp
4005f6: 41 5c pop %r12
4005f8: c3 retq
4005f9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
400600: 8b 33 mov (%rbx),%esi
400602: 31 c0 xor %eax,%eax
400604: bf a8 06 40 00 mov $0x4006a8,%edi
400609: 48 83 c3 04 add $0x4,%rbx
40060d: e8 5e fe ff ff callq 400470 <printf@plt>
400612: 49 39 dc cmp %rbx,%r12
400615: 75 e9 jne 400600 <write_vector(int*, int*, bool)+0x40>
400617: eb db jmp 4005f4 <write_vector(int*, int*, bool)+0x34>
400619: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
Nous pouvons voir qu’au début de la fonction, nous vérifions la valeur et passons à l’une des deux boucles possibles:
4005cf: 84 d2 test %dl,%dl
4005d1: 74 2d je 400600 <write_vector(int*, int*, bool)+0x40>
Bien entendu, cela ne fonctionne que si un compilateur est capable de détecter qu'une condition est invariante. En général, cela fonctionne parfaitement pour les drapeaux et les fonctions simples en ligne. Mais si la condition est "complexe", envisagez d'utiliser des approches provenant d'autres réponses.