Pages

mercredi 29 mai 2013

optimisation de script bash en limitant l'ouverture de processus

Ce qui prend du temps pour un script shell est le nombre de programmes appelés et donc, le nombre de processus créés.

Le meilleur moyen d’accélérer un script est de passer par les builtins et limiter les boucles ouverture/fermeture de programme.
En fait, dans certain cas, on pourrait avoir une commande qui lit sur l'entrée standard et retourne un résultat ; un peu comme bc. C'est là que les coprocess viennent à la rescousse :).

Un coprocess est un sous-shell exécuté de façon asynchrone et fournissant les tubes d'entrée/sortie. Ceux-ci sont accessibles via COPROC[1] et COPROC[2].

Dû coup, avec bc, la méthode est d'écrire dans un tube et lire dans l'autre. Comme la lecture est bloquante, le script va attendre que bc retourne le résultat.
Et voilà l'ouverture d'un programme remplacé par une lecture/écriture.

Par exemple, un petit programme qui lit un fichier contenant des opérations mathématiques et les affiches suivies du résultat.

#!/bin/bash
[ -z "$1" ] && echo $0: file >&2 && exit 1

#coprocess de bc avec la sortie d'erreur redirigée vers la sortie standard
coproc bc 2>&1

while read l; do
 echo "$l" >&${COPROC[1]} # écrire dans le coprocess
 read result <&${COPROC[0]} # réception du résultat
 echo $l = $result
done < "$1"

Sans coproc, la boucle est plus simple mais un processus sera ouvert pour chaque ligne du fichier, ce qui est particulièrement coûteux.

#sans coproc
while read l; do
 echo "$l" = `echo "$l" | bc 2>&1`
done < "$1"

À tester avec par exemple un fichier de calcul comme celui-ci

2+3
2+3*9
23*9
23s*9
3*9

mardi 21 mai 2013

printf vs cout vs boost::format vs tinyformat

Après diverses recherches et comparaisons entre printf et cout, j'ai décidé de faire mon propre benchmark en prenant en compte:

  • La norme utilisée (c++03, c++11)
  • La synchronisation des flux c++ avec ios_base::sync_with_stdio().
  • Le multithreading (option -pthread et inclusion de <thread> ou <pthread.h>).

J'en profite également pour tester et comparer avec boost::format et tinyformat.

Mes tests sont minimalistes et ne couvrent qu'une très petite partie des possibilités mais:

  • La norme utilisée et les threads n'influencent pas les résultats
  • Le fait de désynchroniser cout des streams C fait gagner en performance et ce de façon significative pour certains types.
  • Les performances varient selon le type. Par exemple, les flottants sont plus lents avec les flux c++ mais c'est l’inverse pour les caractères ou les chaînes.
  • boost::format est très lent mais possède l'avantage (ou pas) de mettre une à une les variables à formater. À vrai dire je n'ai pas regardé ce que proposait cette lib.
  • tinyformat est un peu plus lent que les flux C++ (tout dépend du format) mais est une alternative à printf. Tinyformat étant basé sur les flux du C++ la surcharge des flux fonctionne d'office.

Au final la différence entre printf et cout n'est pas importante et varie selon les types de variable, ce qui en rend délicat les mesures.
Cependant, l’avantage d'ostream est la surchargeable qui rend aisée l'ajout de nouveau type à écrire.
Et l'avantage de printf est la possibilité de fournir un format ce qui permet un meilleur contrôle dans l'affichage (ordre des paramètres, internationalisation, ...). Défaut de ostream qui peut être pallié via des libs spécialisées comme tinyformat ou boost::format.

Ci-dessous un tableau un peu brut qui donne des résultats en secondes de la médiane de 10 tests.
Plus lisible ici et mieux étaler ici. Pour finir, des graphiques en bars.
Code source est données: printf_vs_cout.tar.gz (~151 Ko) ou printf_vs_cout.zip (~223 Ko).

Comparaison (en secondes) de différentes APIs de manipulation de flux
format ostream printf boost::format tinyformat
format ostream printf boost::format tinyformat
cpp03
'%d' 0.078679 0.108698 0.464650 0.142462
'%d\n' 0.105090 0.113880 0.489037 0.159989
'%f' 0.463047 0.271212 0.993986 0.494874
'%f\n' 0.501310 0.278252 0.998719 0.505266
'%+6.4f\n' 0.437482 0.264044 0.999680 0.474117
'%c' 0.032680 0.010411 0.423434 0.101393
'%c\n' 0.065180 0.067260 0.444380 0.118883
'%s (chaîne constante)' 0.033918 0.022545 0.473357 0.107968
'%s (chaîne constante)\n' 0.034260 0.022663 0.474171 0.108528
'%s' 0.038505 0.102462 0.442303 0.112725
'%s\n' 0.038247 0.100166 0.440523 0.112081
'%20s\n' 0.199725 0.130075 0.478601 0.289536
'%d blah blah: %s%f\n' 0.637055 0.415993 2.256252 0.762320
cpp03.no_sync
'%d' 0.067002 0.109601 0.476751 0.147007
'%d\n' 0.100137 0.115022 0.492545 0.163944
'%f' 0.397226 0.271981 1.026970 0.503956
'%f\n' 0.441220 0.277866 1.043543 0.514723
'%+6.4f\n' 0.397340 0.262398 0.991949 0.493908
'%c' 0.025976 0.010431 0.447651 0.107648
'%c\n' 0.053353 0.067874 0.457830 0.114286
'%s (chaîne constante)' 0.027245 0.022667 0.457423 0.111958
'%s (chaîne constante)\n' 0.027467 0.022671 0.454262 0.112207
'%s' 0.035850 0.101713 0.450900 0.118588
'%s\n' 0.034884 0.100841 0.454078 0.118410
'%20s\n' 0.091354 0.130033 0.493873 0.188529
'%d blah blah: %s%f\n' 0.617792 0.415237 2.232278 0.799673
cpp03.thread
'%d' 0.076335 0.111163 0.483686 0.143798
'%d\n' 0.105287 0.115094 0.496408 0.160223
'%f' 0.417944 0.272958 0.987420 0.496637
'%f\n' 0.461985 0.279903 1.033756 0.510683
'%+6.4f\n' 0.390526 0.263052 0.980551 0.481614
'%c' 0.032338 0.010354 0.440483 0.103588
'%c\n' 0.065121 0.067937 0.455177 0.120682
'%s (chaîne constante)' 0.034495 0.022692 0.482209 0.110065
'%s (chaîne constante)\n' 0.034517 0.022687 0.479707 0.109049
'%s' 0.039811 0.102205 0.458460 0.113883
'%s\n' 0.039298 0.100060 0.455681 0.111497
'%20s\n' 0.198438 0.130106 0.493480 0.286004
'%d blah blah: %s%f\n' 0.614676 0.418298 2.101005 0.796899
cpp03.thread.no_sync
'%d' 0.068209 0.110208 0.484866 0.145173
'%d\n' 0.100651 0.115347 0.501651 0.161192
'%f' 0.380537 0.274508 0.980840 0.495117
'%f\n' 0.423242 0.279306 0.994300 0.512564
'%+6.4f\n' 0.381993 0.263379 0.963846 0.483052
'%c' 0.025535 0.010461 0.451770 0.106700
'%c\n' 0.051531 0.067833 0.461615 0.114352
'%s (chaîne constante)' 0.026337 0.022717 0.467188 0.110280
'%s (chaîne constante)\n' 0.026454 0.022715 0.468853 0.111218
'%s' 0.035278 0.102782 0.449293 0.117769
'%s\n' 0.034852 0.100985 0.454568 0.117806
'%20s\n' 0.089904 0.129334 0.528948 0.190800
'%d blah blah: %s%f\n' 0.604321 0.416814 2.101355 0.814668
cpp11
'%d' 0.076249 0.109847 0.468822 0.143676
'%d\n' 0.105597 0.116130 0.490844 0.159679
'%f' 0.461125 0.270193 1.009484 0.504168
'%f\n' 0.498769 0.277011 0.999355 0.521951
'%+6.4f\n' 0.422417 0.263603 0.951426 0.489394
'%c' 0.032439 0.062792 0.439045 0.101254
'%c\n' 0.064846 0.067247 0.457943 0.118911
'%s (chaîne constante)' 0.034257 0.100141 0.429901 0.108771
'%s (chaîne constante)\n' 0.034244 0.100071 0.423239 0.108540
'%s' 0.037396 0.102511 0.433076 0.109352
'%s\n' 0.040426 0.100593 0.432691 0.111921
'%20s\n' 0.200948 0.129657 0.457259 0.288542
'%d blah blah: %s%f\n' 0.612473 0.414612 2.107274 0.765568
cpp11.no_sync
'%d' 0.066652 0.110400 0.473127 0.146732
'%d\n' 0.100151 0.115625 0.497890 0.161555
'%f' 0.405104 0.271282 1.008395 0.511858
'%f\n' 0.441258 0.278531 1.023761 0.520053
'%+6.4f\n' 0.395064 0.263529 0.994490 0.503324
'%c' 0.027678 0.062949 0.441796 0.105975
'%c\n' 0.053462 0.067833 0.452007 0.113988
'%s (chaîne constante)' 0.030517 0.099791 0.438610 0.116545
'%s (chaîne constante)\n' 0.030432 0.099902 0.452633 0.116247
'%s' 0.032077 0.102299 0.435142 0.114287
'%s\n' 0.034866 0.100304 0.442736 0.117459
'%20s\n' 0.090916 0.130642 0.482727 0.187818
'%d blah blah: %s%f\n' 0.617891 0.416206 2.125498 0.787507
cpp11.thread
'%d' 0.079597 0.111012 0.490221 0.144572
'%d\n' 0.105356 0.116541 0.509951 0.161204
'%f' 0.419433 0.274043 1.062142 0.500665
'%f\n' 0.459403 0.280520 1.068277 0.516433
'%+6.4f\n' 0.387856 0.264889 1.016386 0.478026
'%c' 0.032373 0.062918 0.468587 0.102139
'%c\n' 0.065148 0.068547 0.503620 0.118624
'%s (chaîne constante)' 0.034375 0.099740 0.452058 0.108760
'%s (chaîne constante)\n' 0.034234 0.100634 0.442354 0.108973
'%s' 0.036803 0.102868 0.447704 0.110269
'%s\n' 0.039003 0.100901 0.457537 0.112071
'%20s\n' 0.198806 0.130172 0.474586 0.289830
'%d blah blah: %s%f\n' 0.604971 0.418086 2.188021 0.812509
cpp11.thread.no_sync
'%d' 0.067131 0.108851 0.479392 0.146475
'%d\n' 0.100783 0.115027 0.503999 0.163044
'%f' 0.388417 0.273822 1.025720 0.483775
'%f\n' 0.419818 0.280391 1.030705 0.493496
'%+6.4f\n' 0.382446 0.264789 0.988382 0.467702
'%c' 0.026136 0.062212 0.461026 0.106458
'%c\n' 0.052622 0.067718 0.464902 0.113045
'%s (chaîne constante)' 0.030520 0.100459 0.443730 0.117889
'%s (chaîne constante)\n' 0.030301 0.100305 0.443197 0.117772
'%s' 0.032202 0.102627 0.448466 0.114538
'%s\n' 0.034557 0.100783 0.449142 0.117577
'%20s\n' 0.091444 0.130085 0.486534 0.188111
'%d blah blah: %s%f\n' 0.609628 0.417566 2.116151 0.818492

mercredi 15 mai 2013

Référence constante sur référence

Une petite note sur les références et le qualificatif const en commençant par un exemple :).

typedef int& reference;
int i = 0;
const reference r = i;
r = 3; //possible car r est en réalité un int&

Comme le montre l'exemple, r est modifiable alors que const est présent. En fait, ajouter const sur une référence ne fait rien. Pareil pour volatile cela dit.

J'aurais pu mettre `int & const r` mais comme const ne peut s'appliquer directement sur une référence, j'ai dû ruser ;).

Si on considère les références comme des pointeurs constants non nuls (car c'est ce qu'elles sont) et en transformant le premier exemple, on obtient:

typedef int * const pointeur_constant;
int i = 0;
const pointeur_constant r = &i;

Donc
decltype(r) = pointeur_constant const = int * const const = int * const = pointeur_constant

À ce moment on se rend compte que le type de r n'est autre que pointeur_constant. const n'a fait qu'ajouter une information déjà présente, il n'a donc aucune utilité.

lundi 22 avril 2013

Placement new, allocateur et container

New est généralement utilisé pour allouer un bloc mémoire et −où il diffère de malloc(),− appelle le constructeur de la classe demandée (si constructeur il y a).
New fait donc deux choses en une.

En fait, new fait une troisième chose: il lance une exception std::bad_alloc si l'espace mémoire est insuffisant.
Pour qu'un pointeur nul soit retourné, il existe le type std::nothrow_t et la variable std::nothrow (tous 2 dans <new>) qui, conjointement avec new s'utilise ainsi:

Machin * machin = new (std::nothrow) Machin(/*params...*/);

Voici ce qui clôt son utilisation courante et voyons comment le faire en deux étapes au moins.

Allouer de la mémoire

Comme on veut juste réserver un espace mémoire, malloc() peut suffire mais prenons les bonnes habitudes, utilisons la fonction ::operator new() !

void * p = ::operator new(sizeof(Machin));

Ou la version sans exception.

#include <new>
//...
void * p = ::operator new(sizeof(Machin), std::nothrow);

Maintenant qu'on a un joli espace mémoire tout fraîchement alloué, construisons l'objet.

Placement new

Le placement new permet de construire un objet dans une zone mémoire prédéfinie et appelle le constructeur.

Machin * machin = new (p) Machin(/*params...*/); // machin == p (même zone mémoire)

Et maintenant que c'est construit, on détruit :D

Destructeur

Un appel explicite au destructeur et le tour est joué.

machin->~Machin();

Étape inutile pour les types scalaires. De toute façon ils n'ont pas de destructeur.

Évidemment, si machin possède un destructeur virtuel et est en réalité un objet héritant de Machin, alors c'est le destructeur de la classe fille qui est appelé.

Il ne reste plus qu'à libérer la mémoire.

Libération de la mémoire

Comme pour ::operator new (), il existe un ::operator delete() auquel il suffit de donner notre pointeur.

::operator delete (p);

Allocation de tableau

::operator new[] et ::operator delete[] fonctionnent de la même façon et sont plus sûrs qu'une gestion manuelle avec leurs homologues sans crochets. Ne serait-ce que pour éviter les fuites mémoire.

void * p = ::operator new [] (sizeof(Machin) * n); //identique que la version sans crochet
Machin * machin = new (p) Machin[n]/*{params...}*/; //si un des constructeurs jettes une exception alors le destructeur des objets construit sera automatiquement appeler.

Surcharge de new et delete

Toutes les formes de new et delete sont surchargeables de façon locale ou globale. Local quand l'opérateur est implémenté à l'intérieur d'une classe (son prototype sera implicitement statique) et global lorsqu'implémenté dans le namespace global.

De plus, comme le new peux prendre des paramètres, il est possible de les personnaliser et d'en ajouter.

#include <new>
struct A{
 A(int i=0)
 {std::cout << "A("<<i<<")" << std::endl;}

 void * operator new (std::size_t size, int x, int y) throw(std::bad_alloc)
 {
  std::cout << "new A " << x << ' ' << y << std::endl;
  return ::operator new (sizeof(A));
 }
};

new(1,2) A;//affiche "new A 1 2 A(0)"

L'alignement mémoire

L'alignement mémoire est une histoire à part entière, je n'en parle donc pas ^^.
Toutefois, renseignez-vous dessus, des variables non alignées peuvent faire chuter les performances et planter certaines architectures de processeur.
Présent dans boost et la dernière norme du C++, il existe aligned_storage et co pour aider dans l'alignement.

Allocateurs

Les allocateurs sont des objets qui s'occupent de faire tout ce qui a été dit auparavant à travers des méthodes comme allocate/desallocate, construct/destroy, address et max_size mais sans faire de surcharge. En fait tous les containers de la std utilisent un std::allocator.

Ce qui m’amène au dernier point, les containers.

Allocateurs et containers

Chaque container (ou presque), que ce soit des std::string, des std::vector ou encore des std::list possèdent tous un allocateur. Son type se définit en dernier paramètre de template.
Évidemment, l'allocateur peut être personnalisable et dans certaines circonstances permet un gain de performance en évitant l'allocation/dés-allocation répéter.

Par exemple, il y quelques semaines, j'ai fait un algorithme qui faisait au total 2'100'000 new pour au final ne garder que 100'000 objets. Donc 2'000'000 de delete.
Dans le pire des cas, il y avait une suite de 25 objets à supprimer. Avec un allocateur qui ne vide pas la mémoire mais garde un tableau des pointeurs alloués je n'avais plus qu'à faire 25 dés-allocations au lieu de 2'000'000 et le nombre de new effectuées descendait quant à lui à 100'025.
Seul le nombre d'appels au destructeur et au placement new restait inchangé. Respectivement 2'000'000 et 2'100'000.

Au final l'algorithme était quand même 30% plus rapide :).

Ce type d'optimisation reste toutefois exceptionnel et n'est pas adapté à tous les containers. Par exemple, std::vector se prête mal à ce genre d'exercice car il demande toujours une allocation d'au moins la taille du nombre d'éléments qu'il possède.
Par contre, les containers comme std::list ou std::map, qui allouent toujours un seul élément à la fois sont un meilleur choix pour utiliser ce type d'allocateur.
Cependant, comme les containers retournent une copie de leur allocateur, il sera difficile de supprimer de manière simple la mémoire non utilisée par l'allocateur du container.

En ce moment, j'ajoute plusieurs allocateurs dans falcon/memory, même si celui dont je viens de parler n'est pas encore présent car son implémentation était vraiment basique et spécifique, il fait quand même partie de ma todo-list.

samedi 23 mars 2013

Nouveau mode pour fopen

Depuis le temps que j’essayais de me rappeler ce nouveau mode. Je ne regardais juste pas la bonne doc. Mais le tir est corrigé, maintenant il est aussi là \o/.

Mais quelle est donc ce nouvel arrivant tout droit sorti du c11 ? Un simple inconnu, le mode "x" :D.
Ce nouveau mode (qui se met en dernier) permet quand il est avec le mode "w" de créer le fichier s'il n'existe pas et d'échouer dans le cas contraire.
Ça fonctionne comme les flags O_CREAT et O_EXCL de la fonction open.

Que de complexité, que de souffrance et que de temps perdu résumés en un simple "x"...

Par contre, je n'ai pas trouvé l'équivalent en C++ pour std::fstream.

dimanche 17 mars 2013

Ne pas empêcher la NRVO

La NRVO et la RVO sont des optimisations des compilateurs pour retourner un objet sans le copier. Je renvoie directement sur une partie de la FAQ C++ de développez.com: d'ici jusqu'à la fin.

Cependant ces optimisations ne s'appliquent pas lorsqu'un appel de méthode qui retourne son instance est directement retourné (une référence sur elle-même).
Ceci:

S f()
{
  S ret;
  return ret.action(); //S& action() { /*...*/ return *this; }
}

Ou cela:

iterator operator+(const iterator& other, int n)
{ return iterator(other) += n; }

Dans ces 2 exemples l'objet est copié inutilement. Pour éviter cette copie, il faut décomposer le code pour que le retour de la fonction ne soit pas lié à une méthode: il faut retourner explicitement l'objet. Ainsi la variable de retour est une variable nommée et la NRVO s'applique.

S f()
{
  S ret;
  ret.action();
  return ret;
}

Et cela:

iterator operator+(const iterator& other, int n)
{
  iterator ret(other);
  ret += n;
  return ret;
}

C'est une optimisation facile à faire et il est tout aussi facile de passer à côté ;).

samedi 9 mars 2013

Petite curiosité de volatile

Voici une petite curiosité du mot clef volatile.

#include <iostream>

struct S{};

void f(void*) { std::cout << 'p'; }
void f(bool) { std::cout << 'b'; }

int main()
{
    S s;
    volatile S * p1 = &s;
    S * p2 = &s;
    f(p1);
    f(p2);
}

Résultat: bp

Le pointeur volatile passe dans la fonction demandant un booléen. Ce n'aurait pas été le cas si un prototype avec volatile void * ou templater sur un _T* existait.

lundi 4 mars 2013

Sqlite, reconstruire la bdd pour l'alléger.

Je ne vais pas par 4 chemins : VACUUM.

Cette commande permet de reconstruire une table et ainsi éliminer les lignes vides et réorganise les index (c'est mieux dit dans la doc ^^).
Certains logiciels se servent de sqlite ; utiliser cette commande de temps en temps sur les tables est une bonne idée.
La première fois que je l'ai fait pour firefox (fichier ~/.mozilla/firefox/nom-du-profil/*.sqlite sur linux) j'ai gagné ½ giga :).

Voici un petit script qui va permettre de le faire sur tous les fichiers sqlite du système (du moins, ceux indexés) et connaître la taille totale avant et après utilisation:

#!/bin/sh
tmpf=/tmp/sqlite_file_path
locate \.sqlite \
| xargs -d'\n' mimetype \
| grep 'application/x-sqlite3$' \
| sed 's/:\s*application\/x-sqlite3\s*$//' \
> $tmpf
xargs --arg-file $tmpf -d'\n' du -hc | tail -n1
while read f ; do
 sqlite3 "$f" 'VACUUM;'
 [ $? -ne 0 ] && echo "\tfor $f"
done < $tmpf
xargs --arg-file $tmpf -d'\n' du -hc | tail -n1
rm $tmpf

Une petite version pour cibler les fichiers:

#!/bin/sh
for f in "$@" ; do sqlite3 "$f" 'VACUUM;'; done

mercredi 27 février 2013

Faites parler votre compilateur

Voici la liste de mes alias pour compiler des codes C, C11, C++ et C++11 et ainsi éviter de nombreuses erreurs de codage et avoir un maximum d'info sur les potentielles erreurs. Les flags les plus importants étant quand même -Wall, -Wextra et -Werror pour transformer les avertissements en erreurs.

alias gcc1x='gcc -std=c1x'
alias g0x='g++ -std=c++0x'
alias g11='g++-4.7 -std=c++11'

alias colorgcc1x='colorgcc -std=c1x'
alias colorg0x='colorg++ -std=c++0x'
alias colorg11='colorg++-4.7 -std=c++11'

flag='-Wall -Wextra -Wundef -Wcast-align -Wformat-security -Wunreachable-code -Wformat=2 -Werror-implicit-function-declaration -Wfloat-equal -Wshadow -Wpointer-arith -Wconversion -Wmissing-declarations -Wmissing-noreturn -Wmissing-format-attribute -Wpacked -Wredundant-decls -Winline -Wdouble-promotion -Winit-self -Wcast-qual -pedantic'
cflag=$flag' -Wstrict-prototypes -Wbad-function-cast -Wmissing-prototypes -Wnested-externs -Waggregate-return -Wwrite-strings'
cppflag=$flag' -Wold-style-cast -Woverloaded-virtual -Wnon-virtual-dtor'
unset flag
alias gwcc="gcc $cflag -Wlong-long"
alias gwcc1x="gcc $cflag -std=c1x"

alias colorgwcc="colorgcc $cflag -Wlong-long"
alias colorgwcc1x="colorgcc $cflag -std=c1x"

unset cflag

alias gw++="g++ $cppflag -Wlong-long"
alias gw0x="g++ $cppflag -std=c++0x"
alias gw11="g++-4.7 $cppflag -std=c++11"

alias colorgw++="colorg++ $cppflag -Wlong-long"
alias colorgw0x="colorg++ $cppflag -std=c++0x"
alias colorgw11="colorg++-4.7 $cppflag -std=c++11"

unset cppflag

alias gfcc='gcc -Wfatal-errors'
alias gf++='g++ -Wfatal-errors'
alias gfcc1x='gcc1x -Wfatal-errors'
alias gf0x='g0x -Wfatal-errors'
alias gfwcc='gwcc -Wfatal-errors'
alias gfwcc1x='gwcc1x -Wfatal-errors'
alias gfw0x='gw0x -Wfatal-errors'

alias colorgfcc='colorgcc -Wfatal-errors'
alias colorgf++='colorg++ -Wfatal-errors'
alias colorgfcc1x='colorgcc1x -Wfatal-errors'
alias colorgf0x='colorg0x -Wfatal-errors'
alias colorgfwcc='colorgwcc -Wfatal-errors'
alias colorgfwcc1x='colorgwcc1x -Wfatal-errors'
alias colorgfw0x='colorgw0x -Wfatal-errors'

Ma version de gcc et la 4.6 par défaut mais la 4.7 (que j'ai aussi) a un meilleur support du C++11.

colorgcc se trouve dans les paquets (d'ubuntu) mais il faudra un patch pour avoir colorg++ (facilement trouvable sur le net).

Je compile généralement mes tests en ajoutant -Wno-missing-declarations pour permettre d'éliminer les warnings lorsque la fonction n'a pas de prototype ou n'est pas inline.

vendredi 22 février 2013

Nouveauté sur les unions

Avant le C++11 les types mis dans les unions se limitaient aux objets POD.
Maintenant plus de limite, faut juste bien appeler le destructeur de l'objet construit dans l'union au risque de jolie fuite mémoire :).

union U{
  int i;
  std::string s;

  U():i(){} //constructeur obligé car s n'est pas un objet POD
  ~U(){} //destructeur qui ne fait rien mais aussi obligé

  void construct_s(const std::string& str)
  { new (&s) std::string(str); } //inclure <memory>

  void destroy_s()
  { s.~basic_string(); } //note string = basic_string<char>
};

Comme tel, c'est un peu limité mais c'est pour montrer...