dimanche 26 août 2012

Oeuf de canard - Un graphe de scène en OCaml

Je ressors une vieillerie, légèrement dépoussiérée: j'avais posté il y a bien longtemps une vidéo d'une planète terre tournoyant, mais je n'avais pas partagé le code. C'est maintenant fait, et mon graphe de scènes en OCaml est maintenant dans Git. C'est assez trivial, si ce n'est quelques difficultés au niveau du calcul du frustum et de l'héritage en OCaml (j'utilise les fonctionnalités objet).

C'est suffisant pour s'amuser, même si un parseur dans un format 3D standard ne serait pas de trop (j'avais commencé avec les fichiers Wavefront .obj). OpenGl a cependant beaucoup évolué depuis, donc c'est très old school.

jeudi 23 août 2012

Postgresql - Méthodes de classes

J'ai lu avec intérêt aujourd'hui un article décrivant Postgres en tant que système de base de données relationnelle-objet. Était évoquée notamment une fonctionnalité que je ne connaissais pas: une fonction sur une table peut être appelée directement sur la table via un '.' au lieu d'avoir à utiliser l'appel usuel avec parenthèses. Démonstration:

create table resultats(
 equipe text not null,
 victoires integer not null,
 nuls integer not null,
 defaites integer not null);

insert into resultats values('Troyes', 3, 0, 1);
insert into resultats values('Sète', 1, 2, 0);

create function total(resultats) returns integer as
$$
 select $1.victoires + $1.nuls + $1.defaites;
$$ language sql;

create function points(resultats) returns integer as
$$
 select $1.victoires * 3 + $1.nuls * 2 + $1.defaites;
$$ language sql;

Si l'on veut visualiser le nombre total de matchs et les points de chaque équipe, l'on peut utiliser la requête suivante:

select r.equipe, total(r), points(r) from resultats r;

Mais il est également possible de l'écrire comme ça:

select r.equipe, r.total, r.points from resultats r;

En dehors de l'aspect purement syntactique de la chose, c'est aussi une manière de considérer les fonctions total et points comme étant des méthodes sur la classe resultats.

vendredi 17 août 2012

La bonne référence du C++

Je suis passé par la doc de SGI, la doc de gcc, cplusplus.com, mais depuis peu l'on voit fleurir en haut des résultats des moteurs de recherche le site cppreference.com, et je crois qu'il contient tout ce qu'il faut pour mon bonheur:

  • Une description détaillée de la bibliothèque C++
  • Avec la description systématique de tous les paramètres et de tous les types de retour
  • Avec la description systématique de la complexité algorithmique de chaque méthode ou fonction lorsqu'elle est définie
  • Référençant les changements induits par C++11
  • Bourré d'hyper liens, lisible, joli, et sans pub
Alors oui, c'est en anglais, la partie française du site est très incomplète mais n'attend que votre talent de traducteur!

Explicit virtual overrides

class A
{
  virtual void f() const;
};

class B
{
  void f();
};

Regardons le code d'un peu plus près. Le "const" manquant dans la deuxième classe, est-ce voulu, ou est-ce une erreur du programmeur? La fonctionnalité C++11 "explicit virtual overrides" ajoute des mot-clés au langage pour permettre de spécifier très précisément le type d'héritage désiré. Démarrons sans plus attendre.

override

Le mot-clé override indique que la méthode doit absolument hériter, le compilateur jettera donc une erreur si ce n'est pas le cas. Par exemple:

class A
{
  virtual void f();
};

class B : public A
{
  virtual void f() override;
};

class C : public A
{
  virtual void f() const override;
};

La déclaration de f dans la classe C provoque l'erreur suivante: error: ‘virtual void C::f() const’ marked override, but does not override. Facile!

final

Le mot-clé final, bien connu des javaistes et attendu depuis longtemps du côté C++, peut s'appliquer soit à une classe, soit à une méthode. Dans le premier cas, le compilateur interdira d'hériter de la classe, alors que dans le second, il interdira l'héritage de la méthode.

class A
{
  virtual void f() final;
};

class B final : public A
{
  virtual void g();
};

class C : public A
{
  virtual void f() override;
};

class D : public B
{
};

La définition de la méthode f dans la classe C provoque le message d'erreur suivant: error: overriding final function ‘virtual void A::f()’, indiquant que la classe f a été marquée finale dans une classe parente. La définition de la classe D provoque l'erreur suivante: error: cannot derive from ‘final’ base ‘B’ in derived type ‘D’, indiquant que la classe D tente de dériver d'une classe finale.

Voilà des fonctionnalités qui vont, je l'espère, d'éviter beaucoup de maux de tête. En indiquant clairement au niveau de la classe parente quelles méthodes ne peuvent être héritées, et en indiquant clairement dans la classe fille les méthodes qui viennent d'une classe de base, l'on documente le comportement attendu de ses objets en laissant le compilateur en faire la vérification.

Cependant, l'exercice a ses limites: contrairement au const qui a un côté viral en forçant tous les types associés à être const-corrects, les mot-clés d'héritage peuvent être utilisés autant ou aussi peu que le programmeur le souhaite. Il faudra donc s'assurer qu'au niveau d'un projet, tout le monde suit le même standard, sinon le remède risque d'être pire que le mal: un programmeur pourra être persuadé que le code est faux car il manque un override, alors que l'auteur avait simplement décidé de l'ignorer.

mardi 14 août 2012

Extended friend declarations - Je zappe

J'ai lu et relu la spec, fait quelques tests, et je ne vois toujours pas vraiment ce que cette fonctionnalité ajoute au standard. De ce que j'en comprends, les déclarations de friend sont plus génériques et plus cohérentes, mais c'est à peu près tout... J'ai essayé quelques exemples pour trouver quelque chose qui ne marchait pas avant et qui marche maintenant, mais je n'ai rien. Si quelqu'un a un test, je suis preneur!

lundi 6 août 2012

User-defined literals

Plongeons immédiatement dans une fonctionnalité nettement plus discutable de C++11, les "User-defined literals". Vous souvenez-vous du petit "f" à la fin des nombres pour indiquer qu'il s'agit d'un flottant? Eh bien, ceci est la généralisation du concept. L'idée est de fournir la possibilité de créer des opérateurs qui travaillent sur certains types de base pour retourner un type défini par l'utilisateur, via un suffixe. Démarrons tout de suite avec un exemple: définissons un nouveau suffixe _str qui transforme une chaine vers un std::string.

#include <string>
#include <iostream>

std::string operator "" _str(const char * str, size_t length)
{
  return std::string(str, length);
}

int main()
{
  std::cout << "abc"_str + "def"_str << std::endl;
  return 0;
}

En définissant l'opérateur "" suivi du suffixe choisi, l'on peut maintenant transformer nos chaînes en std::string directement, nous permettant d'utiliser un + qui aurait sinon fait hurler le compilo. Notons qu'il est possible de faire à peu près n'importe quoi dans l'opérateur "", comme de bidouiller la chaîne, ou d'en retourner la longueur, ou quoi que ce soit d'autre. Le type de retour est librement défini par l'utilisateur.

La liste des types que peut prendre l'opérateur "" en paramètre est la suivante:

  • char const *, pour les nombres sous forme de chaîne
  • unsigned long long int, pour les entiers
  • long double, pour les flottants
  • char const *, size_t, pour les chaînes
  • wchar_t const *, size_t, pour les chaînes de caractère wide
  • wchar16_t const *, size_t, pour les chaînes de caractères 16 bits
  • wchar32_t const *, size_t, pour les chaînes de caractères 32 bits
Petites explications supplémentaires sur le premier élément: il s'agit de lire un nombre directement sous forme de chaîne, le compilateur s'abstenant alors d'en faire la transformation préalable vers un entier. Cet opérateur peut être utile si l'on veut lire de très longs entiers, par exemple dans une bibliothèque de calcul en précision infinie. Voyez l'exemple suivant:

#include <string>
#include <iostream>
#include <sstream>

std::string operator "" _miaou(const char * str)
{
  return("***" + std::string(str) + "***");
}

std::string operator "" _ouaf(unsigned long long int i)
{
  std::ostringstream str;
  str << i;
  return "***" + str.str() + "***";
}

int main()
{
  std::cout << 1234567890123456789012345678901234567890_miaou << std::endl;
  std::cout << 1234567890123456789012345678901234567890_ouaf << std::endl;  

  return 0;
}

À l'exécution, s'affiche:

***1234567890123456789012345678901234567890***
***12446928571455179474***

Les plus observateurs d'entre vous auront remarqué que les opérateurs ne prennent pas d'entiers négatifs. En effet, -123_miaou est interprété comme operator-(operator "" _miaou(123)). Il faudra donc redéfinir operator- si vous voulez retourner un type bizarre.

J'espère sincèrement que cette fonctionnalité va rester confidentielle, car elle me semble un peu trop facile à abuser. Cependant, je ne demande qu'à être convaincu: une bibliothèque physique permettant d'utiliser les unités SI en suffixe pourrait peut-être permettre d'écrire du code fortement typé qui reste très lisible. À voir.

jeudi 2 août 2012

Delegating constructors

Pas vraiment la fonctionnalité de C++11 qui fera rêver dans les chaumières, mais tout à fait utile dans certains cas, la délégation de constructeurs permet tout simplement d'appeler un constructeur depuis un autre constructeur. Voyons immédiatement un exemple:

#include <string>
#include <iostream>

class A
{
public:
  A():
    _a("abc"),
    _b(5),
    _c(0.4)
  {
    std::cout << "Constructeur par défaut" << std::endl;
  }
  
  A(const std::string & a, double c):
    A()
  {
    _a = a;
    _c = c;
    std::cout << "Deux paramètres" << std::endl;
  }
  
  A(const std::string & message):
    A(message, 0.3)
  {
    std::cout << "Un paramètre" << std::endl;
  }
  
  void print()
  {
    std::cout << _a << ", " << _b << ", " << _c << std::endl;
  }

private:
  std::string _a;
  int _b;
  double _c;
};

int main()
{
  std::cout << "---" << std::endl;
  A a1;
  a1.print();

  std::cout << "---" << std::endl;
  A a2("def", 0.7);
  a2.print();

  std::cout << "---" << std::endl;
  A a3("ghi");
  a3.print();
}

Le programme affiche

---
Constructeur par défaut
abc, 5, 0.4
---
Constructeur par défaut
Deux paramètres
def, 5, 0.7
---
Constructeur par défaut
Deux paramètres
Un paramètre
ghi, 5, 0.3

Il est aisé de suivre les constructeurs pour trouver ceux qui ont été appelés. Notons cependant une petite restriction: lorsque l'on délègue un constructeur, il n'est pas possible d'avoir quoi ce ce soit d'autre dans la liste d'initialisation, que ce soient des appels de classe parente ou de l'initialisation de membres. C'est pour cela que dans l'exemple, dans le second constructeur, _a et _c reçoivent l'affectation dans le corps du constructeur.

Il est plus généralement raisonnable d'avoir un constructeur générique prenant le plus grand nombre de paramètres, et des constructeurs spécifiques qui délèguent au constructeur générique, afin de permettre par exemple certaines restrictions sur les paramètres.