dimanche 13 mars 2011

g++ 4.5 et C++0x - Les nouveautés

J'ai porté Openrailz sous g++4.5, ce qui me permet d'utiliser plus de fonctionnalités de C++0x, mais a également soulevé quelque problèmes inattendus. Petit topo.

Destructeur virtuel et = default

Tout d'abord, les mauvaises nouvelles. Je ne sais si vous avez déjà souffert du problème, mais je n'ai jamais trouvé une manière complétement satisfaisante d'implémenter le destructeur virtuel en écrivant une interface. L'on peut inliner le code:


class Miaou
{
virtual ~Miaou() {}
};

mais c'est moche. L'on peut mettre le code en dessous:

class Miaou
{
virtual ~Miaou();
};

inline Miaou::~Miaou()
{
}

mais c'est encore plus moche, sans compter la verbosité lorsque la déclaration est un peu plus complexe (gros template, par exemple). Enfin, l'on peut juste mettre la déclaration dans son .cpp, mais on se retrouve avec un .cpp qui ne contient qu'une déclaration vide, et toujours aussi potentiellement verbeuse.

Lorsque j'ai découvert le = default de C++0x, je me suis dit, bonne nouvelle, voilà enfin comment résoudre le problème:

class Miaou
{
virtual ~Miaou() = default;
};

C'est joli, moderne, et ça fait exactement ce que ça dit. Malheureusement, c'est également interdit par le standard, et si g++4.4 laissait passer, g++4.5 le bloque. Le standard interdit en effet de déclarer un destructeur virtuel par défaut à la première déclaration. C'est à dire que pour écrire du code correct, il faut faire:

class Miaou
{
virtual ~Miaou();
};

inline Miaou::~Miaou() = default;

ce qui est tout aussi moche. Retour donc a la case départ.

Strict aliasing rules et boost::optional

J'avais commencé à virer les boost::optional de ma codebase, car ils causaient un ennuyeux warning dans g++4.4:

#include <iostream>
#include <boost/optional.hpp>

int main()
{
boost::optional<int> b;
if(b)
{
std::cout << *b << std::endl;
}

return 0;
}

La compilation avec g++4.4 renvoie ce message:

optional.cpp: In function ‘int main()’:
optional.cpp:10: warning: dereferencing pointer ‘’ does break strict-aliasing rules
optional.cpp:10: note: initialized from here

Alors, bien sûr, l'on peut utiliser le flag -fno-strict-aliasing, mais l'on réduit les performances.

Les discussions sont allé bon train sur les mailing-lists de Boost, mais il semblerait que le code soit en fait bon, et qu'il s'agisse d'un bug de g++4.4. G++4.5 ne se plaint pas sur ce code, et je compte donc bien le réintégrer vite fait, en particulier pour gérer les colonnes nullables dans mon code de base de données (voir le post précédent).

Et bien sûr... Les lambdas!

Va falloir commencer à s'habituer aux listes de captures, et faire gaffe au passage par valeur / référence. Raison de plus pour commencer le plus tôt possible, et en coller partout. 'tention les yeux! Notons également des améliorations sur les variadic templates, et les chaînes de caractères unicode.

samedi 12 mars 2011

Variadic Templates et pqxx

Je me suis lancé de nouveau dans l'utilisation des variadic templates pour simplifier (!) les appels à la base de données. La chose est un poil touffue, et compile avec g++4.5. Tout d'abord,un exemple d'utilisation: on créé un type Query avec les types des paramètres d'entrée et de sortie en paramètres template. L'on peut ensuite faire tourner la requête avec execute, lequel prend les paramètres d'entrée fortement typés. Ensuite, un itérateur permet de récupérer les résultats sous forme de std::tuple.


typedef
db::Query<db::ParamSet<std::string, std::string>,
db::ResultSet<std::string, std::string> >
QueryT;
QueryT query(dbConn, "select $2::text, $1::text");
query.execute("abc", "def");

for_each(query.begin(),
query.end(),
[](const QueryT::ResultType & result)
{
std::cout << std::get<0>(result) << "\t"
<< std::get<1>(result) << std::endl;
});

Et pour ceux que ça intéresse, le code derrière tout ça. Il ne supporte pour l'instant que les chaînes de caractères en entrée, je laisse de côté le long et ennuyeux code pour gérer d'autres types (basé sur les Traits). Le principe est un peu toujours le même: dérouler le variadic template à l'aide de classes récursives partiellement spécialisées. Notez que le ResultSet est instancié, bien que n'ayant pas d'attributs. Lassé de me battre avec le compilo qui ne comprenait pas mes appels de méthodes statiques, j'ai instancié tout ça.

namespace db
{
template <typename ... Args>
class ParamSet;

template<>
class ParamSet<>
{
public:
void prepare(const pqxx::prepare::declaration &)
{
}

pqxx::prepare::invocation & execute(pqxx::prepare::invocation & invocation)
{
return invocation;
}
};

template <typename T, typename ... Args>
class ParamSet<T, Args...>
{
public:
void prepare(const pqxx::prepare::declaration & declaration)
{
m_sub.prepare(declaration("text", pqxx::prepare::treat_string));
}

pqxx::prepare::invocation & execute(pqxx::prepare::invocation & invocation,
const T & data,
Args... others)
{
return m_sub.execute(invocation(data), others...);
}

private:
ParamSet<Args...> m_sub;
};

template<typename ... Args>
class ResultSet;

template<>
class ResultSet<>
{
public:
template<size_t N, typename TUPLE>
void doPopulate(const pqxx::result::tuple::const_iterator &, TUPLE &)
{
}
};

template<typename T, typename ... Args>
class ResultSet<T, Args...>
{
public:
typedef std::tuple<T, Args...> TupleT;

void populate(const pqxx::result::tuple & input, TupleT & output)
{
doPopulate<0>(input.begin(), output);
}

template<size_t N, typename TUPLE>
void doPopulate(const pqxx::result::tuple::const_iterator & it, TUPLE & output)
{
std::get<N>(output) = it->as<T>();
m_sub.doPopulate<N + 1>(it + 1, output);
}

private:
ResultSet<Args...> m_sub;
};

template<typename PARAMSET, typename RESULTSET>
class Query
{
public:
Query(const std::shared_ptr<pqxx::connection> & dbConn,
const std::string & sql):
m_dbConn(dbConn),
m_sql(sql)
{
m_paramSet.prepare(m_dbConn->prepare(sql, sql));
}

template<typename ... Args>
void execute(Args... args)
{
pqxx::work transaction(*m_dbConn, "Transaction");
pqxx::prepare::invocation invocation =
transaction.prepared(m_sql);

pqxx::result result = m_paramSet.execute(invocation, args...).exec();
m_result.resize(result.size());

auto it = result.begin();
auto end = result.end();
auto it_r = m_result.begin();

for(; it != end; ++it, ++it_r)
{
m_resultSet.populate(*it, *it_r);
}
}

size_t size() const
{
return m_result.size();
}

typedef typename RESULTSET::TupleT ResultType;
typedef typename std::vector<typename RESULTSET::TupleT>::const_iterator
const_iterator;

const_iterator begin()
{
return m_result.begin();
}

const_iterator end()
{
return m_result.end();
}

private:
std::shared_ptr<pqxx::connection> m_dbConn;
std::string m_sql;
PARAMSET m_paramSet;
RESULTSET m_resultSet;
std::vector<typename RESULTSET::TupleT> m_result;
};
}

dimanche 6 mars 2011

GnuCash 2.4.2 débarque sur Debian Wheezy

Enfin, les paquets intéressants débarquent dans Debian Testing. Je fais mes comptes depuis plus d'un an déjà (essayez au moins une fois quelques mois pour voir, c'est très intéressant!), j'utilise GnuCash et j'en suis fort satisfait. Peut-être moins joli que Grisbi et KMyMoney, mais il fait le boulot, et c'est le principal.

La fonctionnalité la plus intéressante de cette nouvelle release est la possibilité d'utiliser une base de données au lieu de fichiers XML. Voici donc la marche à suivre pour bouger sur Postgresql.

  • Tout d'abord, assurez-vous que Postgres est installé, avec un compte qui marche et les permissions qui vont bien

  • Installez le paquet libdbd-pgsql

  • Créez la base gnucash avec la commande suivante: createdb gnucash

  • Démarrez gnucash, et ouvrez votre fichier de compte

  • Allez dans File -> Save as, et choisissez "postgres" dans la section "data format". La fenêtre change, et vous demande vos identifiants de connexion. Entrez le login (et le mot de passe si vous en avez un), zappez le baratin de Gnome-Keyring (OK à tout (sauf si vous êtes un accro de sa sécurité)), et basta!

Les plus curieux d'entre vous pourront aller jeter un coup d'œil à la base, et admirer les tables accounts, transactions et splits qui contiennent l'essentiel des données intéressantes.

En dehors de faire son geek, passer à un backend sur base de données est pratique par exemple si l'on a déjà des systèmes de sauvegarde tournant sur la base. Et c'est toujours ça de moins à penser lors d'une migration sur une autre machine.

vendredi 4 mars 2011

Ais-je rêvé? Pas de boost::mutex::is_locked!

Je l'aurais pourtant juré (et j'ai eu l'air bête au boulot à cause de ça), mais contrairement à ce que je croyais et malgré ma certitude que je l'avait déjà utilisée, les {boost,std}::mutex n'ont pas de méthode is_locked qui permettrait de savoir si le mutex est acquis ou pas.

Dès que je mentionne cette fonctionnalité, l'on me regarde avec des grands yeux: "Mais pourquoi voudrais-tu faire ça? Si tu en as besoin, c'est que ton design est pourri / que tu ne sais pas utiliser les mutex / que tu est bête" (rayer la mention inutile). Alors, oui, je comprends bien que la plupart des utilisations d'une telle méthode sont des aberrations. Mais voilà mon cas d'utilisation:

Imaginez que vous avez une belle classe, gentiment thread-safe (et Wikipédia peut aller se rhabiller avec son "à fil sécurisé", non mais...), et donc possédant un beau mutex (privé, mutable, un beau mutex, quoi). Les méthodes publiques acquièrent le mutex, et appellent des méthodes privées qui s'attendent donc à ce que le mutex soit déjà pris, ce qui est donc une précondition. Afin que le prochain gus écrivant sa méthode publique dans ma classe ne casse tout, j'aimerais beaucoup pouvoir effectivement vérifier cette précondition, avec par exemple un assert(m_mutex.is_locked()). Si mon gus oublie d'acquérir le verrou, il aura une belle assertion lors de ses tests unitaires (encore faut-il qu'il écrive des tests unitaires, mais c'est un autre débat).

Et la, généralement, mon interlocuteur change d'avis: "Tiens, c'est vrai, ça serait pratique...".

Les solutions
Car elles existent.

Une possibilité est pour chaque méthode privée s'attendant à être protégée de prendre en arguments un lock (et pas un mutex), inutilisé dans le corps de la méthode. C'est une sorte de rappel, l'appelant de la fonction devant obligatoirement créer un objet lock. Bien sûr, l'on peut le contourner en créant un objet lock vide, ou qui acquiert un autre mutex, ou qui appelle unlock avant de passer l'objet, mais bon, il faut vraiment être pervers. Autre iconvénient: c'est assez moche, car ça bave dans l'interface.

Une autre possibilité est d'écrire la méthode is_locked, en tentant d'acquérir à nouveau le verrou avec un try_lock, et en vérifiant le résultat. Ce n'est pas spécialement efficace, mais dans une assertion virée des builds optimisés, ce n'est probablement pas un problème. Inconvénient: cela ne marche que si le mutex est non récursif. Sinon, c'est cassé.

Décevant.

vendredi 18 février 2011

Brain overload

Entre d'épineux problèmes de design au boulot, et d'épineux problèmes de design sur OpenRailz, on ne peut pas dire que je code beaucoup, à mon grand regret.

C'est que maintenant qu'OpenRailz permet de créer des gares, des rails, des trains, et d'assigner des itinéraires, il est temps de commencer à créer de vraies villes. Pour ce faire, je pense pour l'instant à effectuer un pavage du plan à l'aide de polygones, pour en faire des routes et des zones constructibles. Il serait probablement raisonnablement aisé de créer ainsi une ville complétement procédurale, mais je me heurte cependant aux difficultés d'interface: comment fournir au joueur les outils pour créer ses routes, sans affoler le générateur de ville, et sans causer des mini-trous moches au niveau du pavage?

samedi 12 février 2011

Postgresql 9.0 chez Debian!

Bonne surprise lors de mon aptitude update; aptitude dist-upgrade hebdomadaire (j'aime bien être à jour), les paquets Postgres 9.0 viennent d'atterrir dans Testing. Cela m'a permis de découvrir les outils de mise à jour, qui sont plutôt efficaces.

Par coup de bol, le passage du méta-paquet postgresql de 8.4 à 9.0 ne m'a pas proposé de virer 8.4, puisque j'avais postgis qui en dépendait. Si ce n'est pas votre cas et que la mise à jour vous propose de supprimer 8.4, annulez, et forcez le paquet 8.4 en manuel (m dans aptitude). Maintenant, laisser l'installation ajouter 9.0 sur un autre port.

C'est là que tout devient simple: passez root, et tapez:

pg_dropcluster --stop 9.0 main
pg_upgradecluster 8.4 main

Le nouveau cluster créé par l'installeur Debian est oblitéré, un nouveau cluster est créé, et les bases migrées dessus, et les ports inversés. Une fois ce processus terminé, vérifiez vos bases (et peut-être redémarrer certains services, chez moi Ocsigen s'est fait dessus lors du changement), puis, quand vous êtes contents,

pg_dropcluster 8.4 main

Un peu de nettoyage dans aptitude pour virer tout ce qui est 8.4, et basta!

'tention cependant!

Toutes les dépendances ne sont pas encore arrivées: postgis est encore en 8.4, et pgadmin foire méchamment. Quand bien même les paquets qui vont bien devraient débarquer incessamment sous peu, si vous faites quoi que ce soit d'important avec votre base, peut-être est-il plus sage d'attendre un poil.

jeudi 10 février 2011

La vraie brochette d'iguane de Fallout

J'ai découvert dans les rues de Hong Kong la fameuse brochette d'iguane de Fallout!