3 commentaires dimanche 10 avril 2011

Je me devais de faire part de la découverte suivante.

En C#, l'opérateur as permet de combiner deux opérations communes: tester si un objet est convertible en un type, et effectuer la conversion à proprement parler. Si la conversion échoue, ou si la référence est déjà nulle, as retourne null. Ainsi:


var val = ...;

// les deux bouts suivants sont équivalents:
var covt1 = val as Type;
var covt2 = val is Type ? (Type)val : null;


Un bon exemple serait une redéfinition de la méthode Equals:

class T
{
// ...

public override bool Equals(object obj)
{
var other = obj as T;
if(other == null)
return false; // couvre obj == null et !(obj is T)

// utiliser other pour la comparaison
}
}


En ce sens, c'est un équivalent direct du dynamic_cast de C++, lorsque celui-ci est utilisé sur des pointeurs.

Il y a néanmoins un défaut avec ce opérateur: as retourne null quand la conversion échoue, et null n'est pas une valeur valide pour les types valeur (les struct). On ne peut donc pas utiliser as pour caster un struct.

En pratique, ce n'est pas trop problématique: les structs héritent tous de System.ValueType, sont implicitement sealed, et ne peuvent donc pas faire partie d'une hiérarchie de classe. Les downcastings de ce genre sont rares.

Mais il ne faut pas oublier un détail: un struct hérite également de System.Object, et il possible de faire un downcasting de object à un struct (une procédure appelée "unboxing"). Pour ces cas, donc, j'en étais réduit à l'approche verbeuse (is + cast).

Or, j'ai récemment découvert quelque chose d'intéressant: il est possible d'utiliser as avec un type valeur si celui-ci est nullable:

struct V
{
// ...

public override bool Equals(object obj)
{
var other = obj as V?; // le ? est un raccourci pour Nullable<V>
if(!other.HasValue)
return false;

// utiliser other.Value pour la comparaison
}
}


MSDN n'en parle pas, mais c'est très intéressant. Testé avec C# 4.0, donc ce n'est peut-être pas disponible pour les versions précédentes. Nullable ayant été introduit en .NET 2.0, il est certain que ça ne fonctionnera pas avant.

4 commentaires vendredi 1 janvier 2010

Avant de commencer, bonne année 2010 à tous!

La plupart des IDEs pour Java sur le marché (dont les deux plus populaires, Eclipse et NetBeans) sont eux-mêmes écrits en Java. Cela n'est pas vraiment un problème en soit: j'utilise Eclipse pour le développement Java, et NetBeans est de facto l'IDE officiel pour le langage. De plus, écrire dans un langage avec un outil écrit dans le même langage est plutôt commun: Visual Studio est écrit en C++, et ça reste quand même un des meilleurs IDEs C++ existant.

Cherchant toujours n'importe quel prétexte pour me mettre à coder, je me suis dis qu'il serait intéressant de coder un IDE pour Java, avec la particularité qu'il serait écrit en C++. L'objectif étant d'en faire un assez rapide et prenant relativement peu de mémoire (n'importe qui utilisant Eclipse et NetBeans sait à quel point il sont lents au démarrage et qu'ils bouffent les ressources de façon assez impressionnante). Surtout que je n'ai jamais vraiment fait d'interface graphique en C++.

Les fonctionnalités prévues seraient plutôt standard: coloration syntaxique, auto-complétion, refactoring, etc. Je préfère ne pas être trop ambitieux et ne pas parler tout de suite de débogueur.

Je sais, je sais, rien de tout cela n'est facile. Mais en fait, tout ceci n'est qu'un gros prétexte pour ce qui me fascine le plus: l'auto-complétion. Cela impliquerait de construire en mémoire une représentation des classes (avec leurs méthodes et attributs) présentes dans le classpath. Lorsque l'utilisateur entrera du code et aura besoin d'aide de la part de l'IDE, on pourrait ensuite faire une recherche selon divers critères dans cette représentation et présenter les résultats au programmeur, qui n'aurait qu'à choisir l'élément le plus approprié.

Le plus difficile reste donc de construire la représentation en question. Et c'est là que ça se corse. Un compilateur Java insère des métadonnées dans le bytecode généré pour permettre certaines choses comme la réflexion. Il serait donc techniquement possible d'extraire les métadonnées du code pour construire la représentation. Sauf que ce n'est pas simple à faire. Je pourrais bien sûr exécuter javap avec les bons paramètres et ensuite analyser la sortie, mais ça me semble un peu paresseux.

En tout cas, je vais faire des recherches sur le sujet, et je vous reviens avec ce que j'ai trouvé.

1 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.

1 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.