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.