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:

Enregistrer un commentaire