| auteur : Aurélien Regat-Barrel |
Boost est un ensemble de bibliothèques C++ gratuites et portables dont certaines seront intégrées au prochain standard C++ (voir Qu'est-ce que le Library Technical Report (tr1 / tr2) ?). On y retrouve donc naturellement les concepts de la bibliothèque standard actuelle, et en particulier ceux de la STL avec laquelle elle se mélange parfaitement.
Boost est très riche : elle fournit notamment des bibliothèques pour :
La plupart de ces bibliothèques tentent d'exploiter au maximum les possibilités du langage C++.
En fait, Boost se veut un laboratoire d'essais destiné à expérimenter de nouvelles bibliothèques
pour le C++.
Il s'agit donc aussi d'une communauté d'experts (dont plusieurs sont membres du comité ISO de normalisation
du C++) qui mettent un point d'honneur à ce qu'un
maximum de compilateurs et de systèmes soient supportés. Ils débattent aussi de l'acceptation de nouvelles
bibliothèques et l'évolution de celles déjà existantes, préfigurant ainsi ce que à quoi ressemblera certainement
la prochaine bibliothèque standard du C++ (voir Qu'est-ce que C++0x ?).
C'est donc là que réside le grand intérrêt de Boost. Outre son excellence technique et sa license
très permissive (compatible avec la GPL) qui permet de l'utiliser gratuitement dans des projets commerciaux, Boost est aussi
un choix très viable sur le long terme. En effet, on peut légétimement espérer qu'un nombre important
de ses bibliothèques soient un jour standardisées, ce qui en fait un outil dans lequel on peut
investir du temps (et donc de l'argent) sans craindre de tout perdre au bout de quelques années
faute de support ou d'évolution.
|
| auteur : Aurélien Regat-Barrel |
Une bonne partie des bibliothèques qui composent Boost peuvent être utilisées directement, sans nécessiter aucune compilation.
Si, dans un premier temps, votre utilisation de Boost se limite à ce genre de bibliothèque,
installer Boost consiste simplement à rendre ses fichiers d'en-tête accessibles à votre compilateur (INCLUDE PATH).
Référez vous à la documentation de ce dernier pour savoir comment procéder.
Quant aux autres bibliothèques bâties sur des appels système telles que boost::filesystem, boost::date_time,
elles nécessitent auparavant d'être compilées. Pour cela, Boost utilise son propre système de génération de type make : Boost.Jam,
ou plus simplement bjam.
Il faut d'abord compiler ce dernier (ou récupérer une version compilée), avant de l'utiliser pour compiler la bibliothèque.
Pour plus d'information sur la compilation de Boost, référez-vous à la documentation disponible en ligne ou encore
dans le répertoire /tools/build/.
Pensez aussi à effectuer une recherche sur nos forums.
|
| auteur : Aurélien Regat-Barrel |
La principale source d'information sur Boost est la documentation officielle de chaque bibliothèque disponible sur le site de Boost.
En dehors de cela, il existe malheureusement assez peu de références sur le sujet.
Ainsi que quelques livres (en anglais) :
Et bien sûr la présente FAQ qui comporte une section consacrée à Boost.
|
| auteurs : Aurélien Regat-Barrel, Laurent Gomila |
Boost met à notre dispositions plusieurs types de pointeurs intelligents (voir Boost Smart Pointers). Les plus couramment utilisés sont boost::shared_ptr et boost::shared_array (pour les tableaux) qui sont des pointeurs intelligents fonctionnant par comptage de référence :
# include <iostream>
# include <string>
# include <boost/shared_ptr.hpp>
class Test
{
public :
Test ( const char * Name ):
name ( Name )
{
}
~ Test ()
{
std:: cout < < " Destruction de " < < this - > name < < ' \n ' ;
}
void printName ()
{
std:: cout < < this - > name < < ' \n ' ;
}
private :
std:: string name;
} ;
typedef boost:: shared_ptr< Test> TestPtr;
int main ()
{
TestPtr ptr;
{
TestPtr ptr_tmp ( new Test ( " objet1 " ) );
ptr = ptr_tmp;
}
ptr- > printName ();
ptr.reset ( new Test ( " objet2 " ) );
ptr- > printName ();
TestPtr ptr2 = ptr;
ptr.reset ();
ptr2.reset ();
ptr- > printName ();
}
|
Ce programme produit le résultat suivant s'il est compilé pour ne pas ignorer les assertions :
objet1
Destruction de objet1
objet2
Destruction de objet2
Assertion failed: px != 0
Si la donnée manipulée est une ressource un peu particulière, et ne doit pas être libérée via delete, on peut spécifier via un foncteur
le comportement à adopter lors de la libération du pointeur intelligent :
struct Delete
{
void operator ()(Test* & Ptr) const
{
cout < < " Destruction " ;
delete Ptr;
}
} ;
int main ()
{
shared_ptr< Test> Ptr (new Test (), Delete ());
}
|
|
lien : Les pointeurs intelligents en C++ par Loïc Joly
|
| auteur : Aurélien Regat-Barrel |
Elles sont très nombreuses, et équivalentes à celles sur les pointeurs bruts dans leur grande majorité.
Prenons l'exemple de base suivant :
# include <boost/shared_ptr.hpp>
class Base
{
public :
virtual ~ Base () { } ;
} ;
class Derived : public Base { } ;
typedef boost:: shared_ptr< Base> BasePtr;
typedef boost:: shared_ptr< const Base> BaseConstPtr;
typedef boost:: shared_ptr< Derived> DerivedPtr;
typedef boost:: shared_ptr< const Derived> DerivedConstPtr;
|
L'upcasting est bien évidemment implicite, comme il le serait pour un pointeur brut :
void implicit_upcasting ()
{
{
Derived * d = new Derived;
Base * b1 = d;
const Base * b2 = d;
delete d;
}
{
DerivedPtr d ( new Derived );
BasePtr b1 = d;
BaseConstPtr b2 = d;
}
}
|
Concernant le downcasting et le constcasting, il est nécessaire de recourir à des fonctions libres spécialisées :
void down_casting ()
{
{
Base * b = new Derived;
Derived * d1 = static_cast < Derived* > ( b );
Derived * d2 = dynamic_cast < Derived* > ( b );
assert ( d2 ! = 0 );
delete b;
}
{
BasePtr b ( new Derived );
DerivedPtr d1 = boost:: static_pointer_cast< Derived> ( b );
DerivedPtr d2 = boost:: dynamic_pointer_cast< Derived> ( b );
assert ( d2 ! = 0 );
}
}
void const_casting ()
{
{
const Base * b_const = new Base;
Base * b2 = const_cast < Base* > ( b_const );
delete b_const;
}
{
BaseConstPtr b_const ( new Base );
BasePtr b2 = boost:: const_pointer_cast< Base> ( b_const );
}
}
|
Les trois fonctions de conversions présentées ci-dessus :
- const_pointer_cast ;
- static_pointer_cast ;
- dynamic_pointer_cast.
ont été ratifiées par le commité de normalisation ISO et incluses dans le Technical Report 1 (tr1). Ce n'est pas le cas de quatre autres fonctions, qui ont été déclarées obsolètes :
- shared_static_cast ;
- shared_dynamic_cast ;
- shared_polymorphic_cast ;
- shared_polymorphic_downcast.
Les deux premières sont respectivement équivalentes à static_pointer_cast et dynamic_pointer_cast, et leur usage est donc fortement découragé. Les deux dernières en revanche n'auront pas d'équivalent dans le prochain standard. Elles correspondent en fait à boost::polymorphic_cast et boost::polymorphic_downcast appliqués aux shared_ptr (voir Comment utiliser les pointeurs intelligents de Boost ?).
L'exemple suivant illustre une possible utilisation de ces deux fonctions :
void deprecated_casting ()
{
{
Base * b = new Derived;
try
{
Derived & d1 = dynamic_cast < Derived& > ( * b );
}
catch ( const std:: bad_cast & )
{
}
# ifdef _DEBUG
Derived * d2 = dynamic_cast < Derived* > ( b );
assert ( d2 );
# else
Derived * d2 = static_cast < Derived* > ( b );
# endif
delete b;
}
{
BasePtr b ( new Derived );
try
{
DerivedPtr d1 = boost:: shared_polymorphic_cast< Derived> ( b );
}
catch ( const std:: bad_cast & )
{
}
DerivedPtr d2 = boost:: shared_polymorphic_downcast< Derived> ( b );
}
}
|
La décision d'utiliser ou non ces deux fonctions vous incombe. Soyez simplement conscient que si vous le faites, vous rendrez votre code plus difficile à migrer le jour où vous souhaiterez utiliser les shared_ptr standards.
Pour terminer, rappelons qu'il est possible de construire un shared_ptr à partir d'un std::auto_ptr (qui est alors invalidé par le shared_ptr construit), ce qui peut s'apparenter en quelque sorte à un cast d'auto_ptr en shared_ptr.
std:: auto_ptr< int > p1 ( new int );
boost:: shared_ptr< int > p2 ( p1 );
|
|
| auteur : Aurélien Regat-Barrel |
boost::conversion introduit quatre types de cast sous forme de fonctions templates libres :
- polymorphic_cast ;
- polymorphic_downcast ;
- lexical_cast ;
- numeric_cast.
polymorphic_cast s'utilise comme dynamic_cast, mais contrairement à ce dernier
qui possède un comportement différent en fonction du type casté (en cas d'erreur),
polymorphic_cast lève systématiquement une exception std::bad_cast en cas
d'échec. Son comportement est donc le même que celui de dynamic_cast en cas
de conversion de références, et c'est précisément pourquoi polymorphic_cast
n'est pas prévu pour être utilisé avec ces dernière.
Notez que polymorphic_cast peut être utilisé pour effectuer du
cross-casting.
Si vous utilisez dynamic_cast pour effectuer du downcasting (ou crosscasting) qui ne devrait
jamais échouer, pensez à utiliser polymorphic_cast qui vous économisera de tester le résultat
du cast et permet aussi de mieux signaler dans le code l'intention d'effectuer un cast qui
ne devrait pas échouer.
Si l'utilisation de dynamic_cast vous procure des problème de performance dans votre programme,
(ce qui devrait traduire un problème de conception, voir Pourquoi l'utilisation du downcasting est-il souvent une pratique à éviter ?)
la solution habituelle est d'utiliser static_cast en remplacement.
Ce dernier est bien plus performant, mais aussi beaucoup plus risqué dans la mesure
où le compilateur vous fait pleinement confiance, et est incapable de vous signaler une erreur
(ce que dynamic_cast ou polymorphic_cast savent faire).
polymorphic_downcast est une sorte de compromis entre ces deux choix.
Compilé en version de développement (DEBUG), polymorphic_downcast se comporte
un peu comme polymorphic_cast, sauf qu'en cas d'échec une assertion failure est
déclenchée au lieu d'une exception.
Dans le code de production (RELEASE), son appel est remplacé par un simple appel
à static_cast, permettant ainsi d'obtenir un programme final performant sans
trop pénaliser la fiabilité.
polymorphic_downcast est malgré tout à utiliser avec retenu, en tant
qu'optimisation après qu'un problème de performances ait été identifié,
et si ce dernier ne peut pas être résolu en reconsidérant le design de l'application.
A noter aussi, que contrairement à dynamic_cast et donc polymorphic_cast,
polymorphic_downcast ne peut pas être utilisé pour du crosscasting.
|
| auteur : Aurélien Regat-Barrel |
Le programme suivant illustre comment utiliser boost::tokenizer pour découper une chaîne de caractères selon des séparateurs par
défaut, ou selon une liste de séparateurs bien précis :
# include <iostream>
# include <boost/tokenizer.hpp>
void split ( const std:: string & Msg )
{
boost:: tokenizer< > tok ( Msg );
for ( boost:: tokenizer< > :: const_iterator i = tok.begin ();
i ! = tok.end ();
+ + i )
{
std:: cout < < * i < < ' \n ' ;
}
}
void split ( const std:: string & Msg, const std:: string & Separators )
{
typedef boost:: tokenizer< boost:: char_separator< char > > my_tok;
boost:: char_separator< char > sep ( Separators.c_str () );
my_tok tok ( Msg, sep );
for ( my_tok:: const_iterator i = tok.begin ();
i ! = tok.end ();
+ + i )
{
std:: cout < < * i < < ' \n ' ;
}
}
int main ()
{
std:: cout < < " -- exemple 1 --\n " ;
split ( " mot1;mot2; ;mot3;;mot4;mot5; " );
std:: cout < < " -- exemple 2 --\n " ;
split ( " mot-compose1;mot,compose2;[mot][compose3];mot compose4;<mot><compose><5> " , " ; " );
}
|
Ce programme produit le résultat suivant :
-- exemple 1 --
mot1
mot2
mot3
mot4
mot5
-- exemple 2 --
mot-compose1
mot,compose2
[mot][compose3]
mot compose4
<mot><compose><5>
|
Notez que les token vides (";;" par exemple) ne sont pas pris en compte.
|
| auteur : Aurélien Regat-Barrel |
La première combine std::for_each avec un foncteur de Boost :
boost::checked_deleter, et la seconde
utilise Boost.Foreach :
Utilisation de checked_deleter | # include <list>
# include <algorithm>
# include <boost/checked_delete.hpp>
int main ()
{
std:: list< int * > l;
l.push_back (new int (5 ));
l.push_back (new int (0 ));
l.push_back (new int (1 ));
l.push_back (new int (6 ));
std:: for_each (l.begin (), l.end (), boost:: checked_deleter< int > ());
return 0 ;
}
|
Utilisation de BOOST_FOREACH | # include <list>
# include <algorithm>
# include <boost/foreach.hpp>
int main ()
{
std:: list< int * > l;
l.push_back (new int (5 ));
l.push_back (new int (0 ));
l.push_back (new int (1 ));
l.push_back (new int (6 ));
BOOST_FOREACH ( int * pi, l )
{
delete pi;
}
return 0 ;
}
|
|
| auteur : Alp Mestan |
En programmation, dans certains langages, on a ce que l'on appelle les
types somme. Il s'agit en fait de décomposer un type T en plusieurs
sous-types T1, T2, ..., TN. Une instance de T peut être obtenue par
une valeur de type T1 ou T2 ou T3, mais pas deux types à la fois. Cela
correspond vaguement aux unions présentes en C et en C++.
Par exemple, si vous réalisez un interpréteur d'expressions
mathématiques du type '1+2-4', alors vous construirez généralement un
arbre d'expressions, une expression étant soit un nombre, soit
une opération (+, -, ...) mettant en relation deux nombres,
qui elle-même résultera en un nombre. Toutefois une expression ne peut
pas être à la fois un nombre et une opération mettant en relation
deux expressions.
Dans les deux cas, nous décomposons notre type 'expression' qui
peut-être vu comme une union disjointe de deux types. C'est ce à quoi
sert le type union en C et C++, toutefois il ne permet pas de gérer
des classes dès qu'elles ont un constructeur par exemple.
En C++, un telle décomposition est rendue possible (bien que moins
puissante et absolument pas intégrée au langage lui-même) grâce à
Boost.Variant. En effet, nous pouvons définir un type C++ qui
représente également l'union disjointe de deux ensembles.
class A { } ;
class B { } ;
class C { } ;
class D { } ;
boost:: variant< A,B,C,D,std:: string,int > v;
v = A ();
v = B ();
v = C ();
v = D ();
v = " Salut " ;
v = 42 ;
|
Un début de piste pour notre interpréteur d'expressions
arithmétiques serait :
struct Op
{
enum op_type { ADD, SUB } ;
double e1, e2;
op_type op;
} ;
double compute_op (const Op& o)
{
switch (o.op)
{
case Op:: ADD:
return o.e1 + o.e2; break ;
case Op:: SUB:
return o.e1 - o.e2; break ;
}
}
typedef boost:: variant< double , Op> expression;
double compute_expression (const expression& e)
{
if ( double * d = boost:: get< double > (& e) )
{
return d;
}
Op* o = boost:: get< Op> (& e);
return compute_op (* o);
}
|
|
lien : Comment récupérer la valeur contenue dans un boost::variant ?
lien : Qu'est-ce que boost::any et boost::variant et quand les utiliser ?
|
| auteur : Alp Mestan |
La fonction template boost::get, définie dans
<boost/variant/get.hpp>, est un premier moyen de récupérer
la valeur d'un boost::variant. Il en existe 4 versions :
template < typename U, typename T1, typename T2, ..., typename TN>
U * get (variant< T1, T2, ..., TN> * operand);
template < typename U, typename T1, typename T2, ..., typename TN>
const U * get (const variant< T1, T2, ..., TN> * operand);
template < typename U, typename T1, typename T2, ..., typename TN>
U & get (variant< T1, T2, ..., TN> & operand);
template < typename U, typename T1, typename T2, ..., typename TN>
const U & get (const variant< T1, T2, ..., TN> & operand);
|
- La première version travaillera sur un pointeur vers
boost::variant pour vous retourner un pointeur vers la
valeur voulue.
- La seconde version travaillera sur un pointeur vers un
boost::variant constant pour vous retourner un pointeur vers
la valeur constante voulue.
- La troisième version travaillera sur une référence vers
un boost::variant pour vous retourner une référence sur
la valeur voulue.
- La quatrième version travaillera sur une référence sur
un boost::variant constant pour vous retourner une référence
sur la valeur constante voulue.
Dans le cas de (1) et (2), si le get échoue (si votre variant contient
un int et que vous appelez get<string>(v) par exemple), alors
la fonction vous retourne un pointeur nul.
Dans le cas de (3) et (4), si le get échoue, la fonction lance une
exception bad_get (qui dérive de std::exception et définit donc la
fonction what() décrivant ce qu'il s'est passé).
De manière générale, la fonction get échouera (retournera un pointeur
nul pour (1) et (2), lancera une exception bad_get pour (3) et (4))
si la valeur courante contenue dans votre boost::variant n'est pas
du type demandé explicitement avec get.
Pour terminer, un petit exemple d'utilisation :
# include <iostream>
# include <boost/variant.hpp>
int main ()
{
boost:: variant< int , std:: string> v;
v = 42 ;
int * i = boost:: get< int > (& v);
assert (* i = = 42 );
* i = 84 ;
std:: string* s = boost:: get< std:: string> (& v);
assert (s = = NULL );
int & i2 = boost:: get< int > (v);
assert (i2 = = 84 );
try
{
std:: string& s = boost:: get< std:: string> (v);
}
catch (std:: exception& e)
{
std:: cout < < " Exception ! " < < e.what () < < std:: endl;
}
return 0 ;
}
|
Ce code provoquera donc l'affichage suivant : Exception !
boost::bad_get : failed value get using boost::get.
|
| auteur : Herb Sutter |
Il y deux moyens pour simuler un typage faible en C++.
On parle bien de simulation, le langage reste typé statiquement.
Le premier d'entre eux est Boost variant :
boost:: variant< int , string > x;
x = 42 ;
x = " hello, world " ;
x = new Widget ();
|
Contrairement à une union, boost::variant peut inclure n'importe
quel type, mais vous devez spécifier quels types sont autorisés.
Vous pouvez même simuler un comportement polymorphe ad-hoc (surcharge
de fonctions) avec boost::apply_visitor qui sera en plus vérifié à la
compilation.
L'autre moyen est boost::any :
boost:: any x;
x = 42 ;
x = " hello, world " ;
x = new Widget ();
|
Contrairement aux unions, boost::any accepte n'importe quel type.
A l'inverse de boost::variant, boost::any ne vous laisse pas préciser
les types autorisés, ce qui peut être une bonne ou mauvaise chose en
fonction du degré de laxisme souhaité. Aussi, vous n'avez aucun moyen
de simuler une surcharge de fonctions et l'objet doit être alloué
dynamiquement (sur le tas).
De façon intéressante, ceci montre comme le C++ suit de façon ferme et
efficace un schéma de typage statique quand c'est possible et dynamique
quand c'est nécessaire.
Quand avez vous besoin de quoi ?
Utilisez boost::variant quand vous voulez :
- un objet capable de stocker les valeurs
d'un nombre fini de types ;
- une vérification à la compilation du type
visité ;
- une allocation efficace, qui se trouve sur
la pile ;
- et vous pouvez vivre avec d'horribles messages d'erreur
quand le type attribué n'est pas le bon.
Utilisez boost::any quand vous voulez :
- la flexibilité offerte par un objet capable
de stocker virtuellement n'importe quel type ;
- la flexibilité offerte par any_cast ;
- la garantie qu'il n'y aura pas d'exceptions
lancées durant un swap.
|
Consultez les autres F.A.Q.
|
|