I. Présentation de la POA▲
I-A. Les limites de la POO▲
la POO a pour bur but de découper le programme en une série d'objets indépendants qui s'échangent des messages via leur interface publique. Cette indépendance peut être acquise entre les différents objets métiers mais le code sera toujours envahie par une série d'instructions transverse à son but premier comme le logging, la gestion des invariants ou encore le verrouillage dans un contexte multi-thread. Regardons un exemple pour mieux comprendre.
class
Compte
{
//...
int
m_solde;
public
:
void
depot(int
valeur)
{
m_solde +=
valeur;
}
void
retrait(int
valeur)
{
if
(valeur <
m_solde)
m_solde -=
valeur;
else
std::
cerr <<
Impossible de retirer plus que disponible" << std::endl;
}
};
Cette classe Compte, qui gère un compte en banque, est tout ce qu'il y a de plus basique. Imaginons qu'a chaque transaction on souhaite enregistrer l'ancien solde, la transaction, et le nouveau solde. On peut faire quelque chose dans le style suivant:
class
Compte
{
// ...
public
:
void
depot(int
valeur)
{
Logger <<
"Votre solde"
<<
m_solde <<
"avec un depot de "
<<
valeur <<
"est de "
;
m_solde +=
valeur;
Logger <<
m_solde;
}
void
retrait(int
valeur)
{
if
(valeur <
m_solde)
{
Logger <<
"Votre solde"
<<
m_solde<<
"avec un retrait de "
<<
valeur <<
"est de "
;
m_solde -=
valeur;
Logger <<
m_solde;
}
else
{
std::
cerr <<
"Impossible de retirer plus que disponible"
<<
std::
endl;
}
}
}
;
Pour le code suivant compile, plusieurs solutions.
La première est que Logger est une variable globale. Certains diront que c'est une faute grave mais c'est envisageable, regardez std::cout.
Le problème avec cela, c'est qu'on polue l'espace global et que des risques de collisions sont présents, même avec des namespaces. Enfin, on peut souhaiter appliquer différents traitements de logging en fonction des classes, ce qui est difficilement permis avec les variables globales.
La solution est donc que ce soit la classe qui soit le propriéataire de l'objet Logger. Mais dans ce cas, on perd l'indépendance du code métier, ce dernier est lié à un pan vertical du programme alors que dans un monde idéal, il ne devrait pas.
La POA souhaite arreter cet envahissement en placant le code transverse non plus dans le code métier mais dans des aspects.
I-B. Présentation de la POA et mécanismes▲
I-B-a. Mécanisme▲
Etant donné que la POA souhaite séparer les aspects du code, il parait normal qu'ils soient situés dans fichiers différents que ceux du code source. Mais lors du processus gloable de création du logiciel, ils doivent être intégrés au code source: c'est le rôle du tisseur. Compararons la compilation "classique" et celle avec Aspect C++


Pour les personnes familières avec Qt, on peut dire qu'Aspect C++ est similaire au Moc dans le sens où il opère une opération pré-compilation dans un langage qui est une extension du C++.
I-B-b. Vocabulaire▲
La POA introduit un certain nombre de nouveaux mots de vocabulaire. Regardons les en détails car il est d'une grande importance par la suite
- Aspect: L'aspect est un moyen d'étendre de façon intrusive une un programme via les classes ou les fonctions. Un aspect se comporte de façon similaire à une classe, il peut avoir des données membres (fonctions ou variables) mais il contient aussi les déclarations des advices
- Advice: un advice est une déclaration qui va servir soit à exécuter du code lorsqu'un join point dynamique est atteint soit à introduire des élements dans le(s) joint point statiques
- Joint point: un Joint point est un endroit du code où les aspects peuvent venir s'interfacer. Un joint point dynamique est l'appel ou l'execution d'une fonction quelconque ou encore la création d'un objet. Un joint point statique est
- Pointcut: un Pointcut est un prédicat pour attraper les différents joint point.
- Pointcut expression: une Pointcut expression est une expression servant à définir un pointcut. Elle est composée de match expression (pour trouver les joints points, comme des regex) ainsi que d'opérateurs permettant de cibler certaines fonction en particulier. Le tout est lié par des opérateurs logiques classiques.
- Ordre de déclaration: Si plus d'un aspect doit intervenir sur le même join point, cette instruction permet de spécifier l'ordre d'appel.
- Slice: un slice est un fragment de code qui peut être introduit dans une classe.
II. Aspect C++▲
II-A. Sous Windows▲
II-B. Sous Debian et Ubuntu▲
L'installation sous debian et ubuntu est la plus classique qu'il soit puisqu'il existe un paquet nommé aspectc++; Il suffit donc de faire
aptitude install aspectc++
III. Etude détaillée de Aspect C++▲
III-A. 1er Exemple▲
Reprenons le code du paragraphe d'introduction et rendons le compilable:
#include
<iostream>
template
<
class
U>
class
Logger{
U&
Ref;
public
:
Logger&
operator
<<
(U&
(*
pf)(U&
))
{
pf(Ref);
return
*
this
;
}
template
<
class
T>
Logger&
operator
<<
(const
T&
t)
{
Ref <<
t;
return
*
this
;
}
Logger(U&
r=
std::
cout) : Ref(r)
{}
}
;
class
Compte
{
Logger<
std::
ofstream>
m_log;
int
m_solde;
// ...
public
:
Compte(int
solde=
100
) : m_log(std::
cout), m_solde(solde)
{}
void
depot(int
valeur)
{
m_log <<
"Votre ancien solde est de "
<<
m_solde <<
std::
endl;
m_solde +=
valeur;
m_log <<
"Votre nouveau solde est de "
<<
m_solde <<
std::
endl;
std::
cout <<
std::
endl;
}
void
retrait(int
valeur)
{
if
(valeur <
m_solde)
{
m_log <<
"Votre ancien solde est de "
<<
m_solde <<
std::
endl;
m_solde -=
valeur;
m_log <<
"Votre nouveau solde est de "
<<
m_solde <<
std::
endl;
std::
cout <<
std::
endl;
}
else
{
std::
cerr <<
"Impossible de retirer plus que disponible"
<<
std::
endl;
}
}
}
;
Les aspects liés au logging sont redondants. Voici le code équivalent avec les aspects
#include
<iostream>
#include
"compte.h"
Compte::
Compte(int
solde) : /*m_log(std::cout)*/
m_solde(solde)
{}
void
Compte::
depot(int
valeur)
{
m_solde +=
valeur;
}
void
Compte::
retrait(int
valeur)
{
if
(valeur <
m_solde)
{
m_solde -=
valeur;
}
else
{
std::
cerr <<
"Impossible de retirer plus que disponible"
<<
std::
endl;
}
}
int
main(int
argc, char
const
*
argv[])
{
Compte c(200
);
c.depot(10
);
return
0
;
}
Le header qui va avec ce code:
#ifndef COMPTE_H
#define COMPTE_H
#include
<sstream>
template
<
class
U>
class
Logger{
U&
Ref;
public
:
//pour les manipulateurs de flux, comme std::endl
Logger&
operator
<<
(U&
(*
pf)(U&
))
{
pf(Ref);
return
*
this
;
}
template
<
class
T>
Logger&
operator
<<
(const
T&
t)
{
Ref<<
t;
return
*
this
;
}
Logger(U&
r=
std::
cout) : Ref(r)
{}
}
;
class
Compte
{
int
m_solde;
public
:
Compte(int
solde =
100
);
void
depot(int
valeur);
void
retrait(int
valeur);
}
;
#endif
Et le fichier aspect correspondant
#include
<iostream>
#include
"compte.h"
aspect TraceCompte
{
private
:
pointcut what() =
"Compte"
;
advice what() : slice class
{
protected
:
Logger<
std::
ostream>
m_log;
}
;
advice execution("% ...::%(...)"
) &&
within(what()) &&
that(compte) : around(Compte&
compte)
{
compte.m_log <<
"Avant votre solde etait de "
<<
compte.m_solde <<
std::
endl;
tjp->
proceed();
compte.m_log <<
"Avant nouveau solde est de "
<<
compte.m_solde <<
std::
endl;
}
}
;
Je ne commenterai ici que le code lié à la programmation par aspect.