0 commentaires samedi 10 octobre 2009

Dans le .NET Framework, on retrouve l'interface IEnumerable (ainsi que sa version générique, IEnumerable<T> depuis la version 2.0). Toute classe implémentant cette interface pourra être parcourue avec un foreach.

Java possède, depuis sa version 1.5, un équivalent, l'interface Iterable. Une classe implémentant Iterable pourra être parcourue avec un for.

En C#, j'ai tendance à utiliser le plus possible IEnumerable: c'est le dénominateur commun. Alors quand je me suis mis à faire du Java, j'ai décidé d'employer Iterable pour les mêmes raisons. Sauf que j'ai découvert quelque chose d'étrange.

Avec .NET, les tableaux implémentent IEnumerable. On peut donc passer un tableau à une méthode qui attend un IEnumerable en paramètre. Or, en Java, les tableaux n'implémentent pas Iterable. On peut les parcourir comme un Iterable, mais c'est probablement du à une "patch" du compilateur. Je n'ai aucune idée de pourquoi est-ce que c'est le cas, mais toujours est-il qu'on ne peut donc pas faire la même chose qu'avec .NET.

Mais ce n'est pas vraiment un problème. Il suffit de coder un adaptateur! Et c'est ce que j'ai fait:


import java.util.Iterator;
import java.util.NoSuchElementException;

/**
* Un adaptateur permettant d'utiliser un tableau là où un Iterable<T> est requis
* @author Etienne de Martel
* @param <T> Le type d'un élément du tableau
*/
public class IterableArray<T> implements Iterable<T>
{
// Un itérateur sur un tableau
private class ArrayIterator implements Iterator<T>
{
T[] _array;
int _index;

public ArrayIterator(T[] array)
{
_array = array;
_index = 0;
}

@Override
public boolean hasNext()
{
return _index < _array.length;
}

@Override
public T next()
{
if(!hasNext())
throw new NoSuchElementException();
return _array[_index++];
}

// Cet itérateur est en lecture seule
@Override
public void remove()
{
throw new UnsupportedOperationException();
}

}

private T[] _array;

/**
* Construit un nouvel adaptateur
* @param array Le tableau autour duquel l'adaptateur sera construit
* @throws NullPointerException Le tableau passé en paramêtre est null
*/
public IterableArray(T[] array) throws NullPointerException
{
if(array == null)
throw new NullPointerException("array cannot be null");
_array = array;
}

@Override
public Iterator<T> iterator()
{
return new ArrayIterator(_array);
}

}


Voilà. Have fun.

0 commentaires lundi 28 septembre 2009

Ne vous inquiétez pas, je devrais arriver avec des choses plus intéressantes dans les jours à venir (si j'ai le temps)...

Les templates de C++ permettent d'écrire du code générique, c'est à dire de se faire chier une seule fois pour ensuite pouvoir utiliser le même code partout. Enfin, c'était là leur but premier, et puis un gars (David Vandevoorde, si je me souviens bien) a découvert qu'elles étaient turing complete. Cela signifie qu'on peut écrire, avec des templates, des programmes qui s'exécuteront à la compilation.

L'exemple le plus répandu est une factorielle en O(1):


#include <iostream>

template<int N>
struct Factorial
{
static const int Value = N * Factorial<N - 1>::Value;
};

template<>
struct Factorial<0>
{
static const int Value = 1;
};

int main()
{
std::cout << Factorial<6>::Value; // affiche 720
}
La valeur de la factorielle sera évaluée à la compilation, et sera équivalente à une constante.

On peut également créer des assertions statiques (des tests qui font planter la compilation s'ils ne passent pas) avec des templates. À noter qu'une telle pratique sera rendue obsolète en C++0x, qui inclura un support standard pour de telles assertions.

En Java, pour éviter les abus de downcasts de Object à quelque chose, on a introduit dans la version 1.5 les Generics. C# a eu tôt fait de copier le concept dans sa version 2.0. Mis à part une syntaxe légèrement différente pour les contraintes, la principale nuance entre C# et Java est qu'en Java, les Generics sont remplacés par des Object avec des casts, alors qu'en C#, ils sont remplacés par le type utilisé. Ces deux approches se valent, bien que je préfère celle de C#: elle permet, entre autres, d'effectuer de la réflexion sur les types génériques.

Pour celui qui connaît peu les templates de C++, on serait porté à croire que les Generics sont très semblables, mais il y a énormément de différences entre les deux. La plus importante est que les templates sont instanciés à la compilation, et le compilateur peut donc évaluer immédiatement si elles sont correctement utilisées (et si ce n'est pas le cas, générer un message d'erreur incompréhensible). En Java et en C#, les generics sont instanciés à l'exécution, et le compilateur ne peut donc pas s'assurer qu'elles sont bien utilisées. Le programmeur doit donc insérer des "contraintes", sinon, le type générique sera considéré comme un Object, ce qui tue un peu le principe. De telles contraintes sont inutiles en C++, car les templates répondent au principe de SFINAE.

Pourquoi une telle discussion? J'ai trouvé un vieil article sur MSDN décrivant les différences entre les templates de C++ et les Generics de C#:
http://msdn.microsoft.com/en-us/library/c6cyy67b(loband).aspx
En passant, pour ceux qui ne le savent pas, le "(loband)" dans l'URL, c'est pour activer le "low bandwidth mode" sur MSDN, vous permettant de sauver de la bande passante.

Voilà.

0 commentaires dimanche 9 août 2009

(Désolé pour le titre interminable)

C'est pas compliqué, mais fallait y penser.

Disons que vous avez en tant qu'attribut une std::string, et que vous voulez l'initialiser avec le contenu d'un flux reçu en paramètre au constructeur. Vous pourriez bien sûr choisir l'approche lente:


class Script
{
std::string _script;
public:
Script(std::istream &stream)
{
std::string line;
while(std::getline(stream, line))
_script += line + '\n';
}
// ...
};
Le problème avec cette technique, c'est que l'attribut est d'abord construit avec le constructeur par défaut, puis modifié. Ce problème est dû au fait que par défaut, les objets en C++ ont une sémantique de valeur (ils sont donc traités comme des types primitifs). Si l'exemple ci-dessus avait été en C# ou en Java, _script aurait été une référence null, et donc, aucune mémoire n'aurait été allouée inutilement.

Pour court-circuiter (+10 points pour le beau terme) la construction par défaut, il faut passer par une liste d'initialisation. L'un des constructeurs de std::string prend une paire d'itérateurs représentant une séquence de char (wchar_t pour std::wstring). Avec notre connaissance des std::istream_iterator, on serait donc tenté de faire ça:

Script(std::istream &stream)
: _script(std::istream_iterator<char>(stream), std::istream_iterator<char>())
{
}
Ça compile et ça ne plante même pas! Sauf que ça n'a pas le résultat escompté.

std::istream_iterator utilise l'opérateur >>, lequel a un bien étrange comportement: il consomme tous les caractères blancs (c'est-à-dire ceux pour lesquels std::isspace() retourne true pour la locale actuellement utilisée par le flux). Avec la culture classique (celle du langage C), cela signifie les espaces, les tabulations (\t), les retours chariots (\r) et les retours de ligne (\n). Notre attribut _script ne contiendra donc pas tous les éléments!

La solution consiste donc à utiliser std::istreambuf_iterator, lequel n'effectue aucun traitement sur le contenu du flux:

Script(std::istream &stream)
: _script(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>())
{
}
Naturellement, si vous ne voulez lire qu'une partie du flux, cette technique ne fonctionnera pas.

Voilà! Comme d'habitude, j'attends vos commentaires.

0 commentaires mercredi 5 août 2009

Tcl signifie "Tool Command Language". C'est un langage qui a été conçu spécifiquement pour ajouter des fonctionnalités de scripting à d'autres langages. Puisqu'un binding existe en standard pour le C, il s'en fallu de peu pour que quelqu'un ait la bonne idée d'en faire un wrapper C++.

Ça a pris un peu de gossage, mais je suis arrivé à faire marcher le tout sous Windows avec VC++9 (AKA Visual Studio 2008).

Donc, C++/Tcl est composé de deux fichiers (un .h et un .cpp). Il a également deux dépendances: le core de Tcl et... Boost. Seuls quelques headers de Boost sont utilisés, pas besoin de linker quoique ce soit. Pour Tcl, c'est une autre histoire.

Ah, Boost. Quelle superbe bibliothèque. Je dois dire que je n'étais jamais arrivé à la faire fonctionner. Quelle n'a pas été ma surprise de découvrir qu'une entreprise du nom de BoostPro Computing offrait un installateur permettant d'installer Boost en mode "next next finish". Ne vous inquiétez pas, c'est entièrement gratuit et ça ne corromps pas la licence outre mesure. Vous pouvez obtenir ledit installateur là: http://www.boostpro.com/download. J'ai téléchargé la version 1.39, c'est-à-dire la plus récente au moment d'écrire ces lignes. L'installateur installe par défaut toutes les bibliothèques "header only". On peut également installer d'autres bibliothèques (celle qui doivent être linkées à la compilation). Assurez vous de n'installer que les versions allant avec le compilateur de votre choix (VC++7.1 pour VS2003, VC++8 pour VS2005 ou VC++9 pour VS2008), sinon vous allez télécharger trois fois trop de données que vous n'utiliserez même pas en fin de compte!

L'installateur n'aura peut-être pas ajouté le chemin à vos répertoires d'include standard. Dans ce cas, il vous faudra configurer Visual Studio en allant dans Outils > Options > Projets et solutions > Répertoires de VC++. Dans le menu en haut à droite, sélectionnez "Fichiers Include" (pour les .h) ou "Fichiers bibliothèques" (pour les .lib/.dll), dépendamment de ce que vous voulez configurer.

La meilleure façon de tester si Boost fonctionne est d'y aller avec l'exemple fournis par Boost (à noter que vous Boost.Regex n'est pas installé par défaut avec l'installateur, vous devrez donc avoir coché la case pour que cela fonctionne):


#include <boost/regex.hpp>
#include <iostream>
#include <string>

int main()
{
std::string line;
boost::regex pat( "^Subject: (Re: |Aw: )*(.*)" );

while (std::cin)
{
std::getline(std::cin, line);
boost::smatch matches;
if (boost::regex_match(line, matches, pat))
std::cout << matches[2] << std::endl;
}
}

Maintenant, Tcl. Vous pouvez l'obtenir à cet endroit: http://www.activestate.com/activetcl/ (cliquez sur le "Download Now"). Vous devrez ensuite (encore) configurer Visual Studio. À noter que cette fois-ci, pour que ça ne chie pas à l'édition des liens, j'ai du ajouter "tcl85.lib" aux propriétés de mon projet sous Éditeur de lien > Entrée > Dépendances supplémentaires.

Ok, dernière étape: C++/Tcl lui même. Rendez-vous là: http://sourceforge.net/projects/cpptcl/files/. Bon, il faut ajouter cpptcl.cc et cpptcl.h à votre projet, et vous assurer que le répertoire details fournit avec C++/Tcl soit quelque part où le préprocesseur peut le trouver. Là, problème: le code ne compilait pas pour cause d'ambiguïté entre std::exception et boost::exception. Il faut dire que le gars n'a testé son code que sous GCC. Solution? Retirer le using namespace std en haut de cpptcl.cc, et ajouter std:: partout où c'est nécessaire (en fait, à quelques endroits seulements, là où des std::string sont utilisées).

Je suis allé ramasser le Hello World sur le site de C++/Tcl, et ça marche!

#include "cpptcl.h"
#include <iostream>
#include <string>

using namespace std;
using namespace Tcl;

void hello()
{
cout << "Hello C++/Tcl!" << endl;
}

int main()
{
interpreter i;
i.def("hello", hello);

string script = "for {set i 0} {$i != 4} {incr i} { hello }";

i.eval(script);
}

Bon, je vais voir ce que ça donne. À suivre.

1 commentaires mercredi 22 juillet 2009

Java était (et est toujours, d'ailleurs) un langage "purement orienté objet". Quelle décision étrange. Non seulement Java est, selon moi, moins orienté objet que C++ (presque pas de contrôle sur la fin de vie d'un objet, notamment), mais ça voulait également dire qu'il ne pouvait faire rien d'autre que de la POO. Pourtant, je me dis que du procédural, parfois, c'est utile.

C# répète à peu près les mêmes erreurs que Java pour la POO, mais compense en s'ouvrant à d'autres paradigmes. Dans sa version 3.0, C# a donc mis un pied (ou plutôt le gros orteil) dans le monde de la programmation fonctionnelle avec des lambda expressions et des expression trees (bon, on pouvait déjà faire ça depuis 1998 en C++ avec des templates, mais bel effort quand même). Et depuis sa toute première version, C# supporte un hybride entre méthodes et fonctions libres sous la forme de délégués (un pointeur déguisé sur une fonction, en fait). Bon, d'accord, je pourrais également faire le parallèle avec C++, mais pourquoi taper sur quelqu'un qui est à terre?

Je sais pas pour vous, mais moi, écrire une classe pour ne l'utiliser qu'une seule fois, comme foncteur sans état en plus, je trouve ça lourd. C'est d'ailleurs pourquoi, en C++, je préfère les fonctions aux foncteurs lorsque c'est possible: c'est beaucoup moins verbeux (mais bon, une fonction n'a pas d'état, alors il faut parfois sortir les foncteurs). Alors bon, quand je vois, dans le .NET Framework, une méthode qui attend un IEqualityComparer<T>, je sacre. Pourquoi devrais-je implémenter une classe pour une fonctionnalité qui n'existera que là? Ne serait-il pas plus simple de passer un délégué à la place? Martin Fowler n'a-t-il pas dit que la généricité préventive était néfaste?

J'ai donc retroussé mes manches, et voici ce à quoi je suis arrivé:


public static class EqualityComparer
{
private class DelegateComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, T, bool> _equalityFunction; // prend deux T en paramètre et retourne un bool
private readonly Func<T, int> _hashCodeFunction; // prend un T en paramètre et retourne un int

public DelegateComparer(Func<T, T, bool> equalityFunction, Func<T, int> hashCodeFunction)
{
if (equalityFunction == null || hashCodeFunction == null)
throw new ArgumentNullException();
_equalityFunction = equalityFunction;
_hashCodeFunction = hashCodeFunction;
}

public bool Equals(T x, T y)
{
return _equalityFunction(x, y);
}

public int GetHashCode(T obj)
{
return _hashCodeFunction(obj);
}
}

public static IEqualityComparer<T> Make<T>(Func<T, T, bool> EqualityFunction, Func<T, int> HashCodeFunction)
{
return new DelegateComparer<T>(EqualityFunction, HashCodeFunction);
}
}

Bon, quel intérêt? Eh bien, admettons (cet exemple utilise des lambdas, mais vous pourriez très bien passer des méthodes, tant qu'elles ont la bonne signature):

var absComparer = EqualityComparer.Make<int>(
(p1, p2) => Math.Abs(p1) == Math.Abs(p2),
p => Math.Abs(p).GetHashCode());

Et voilà, un IEqualityComparer<T> en une ligne (étalée sur plusieurs lignes pour plus de clarté)! C'est tu pas wonderful?

Dites-moi ce que vous en pensez. Comme toujours, ce n'est pas final.