samedi 30 août 2014

Grosse mise à jour Debian

Ça occupe! Tout d'abord, Debian est passé à Postgresql 9.4. Depuis 9.0, Postgresql fait tout ce que je veux, je n'attendais pas particulièrement cette dernière version, mais ça fait toujours plaisir d'être à jour. La mise à jour est toujours aussi triviale, et mes vieilles notes toujours d'actualité. Il faut juste ne pas se tromper de cluster.

Ensuite, quelques changements mineurs dans Médoc, avec Eliom qui passe en 4.0, et change quelques noms de module, et une en-tête manquante dans le client C++. Le commit est donc trivial.

Étrangement, sur ma machine virtuelle au boulot, la mise à jour a voulu passer à systemd, mais pas à la maison. Le passage à systemd semble s'être bien passé, mais un souci avec les "Guest Additions" de VirtualBox m'empêche d'en profiter. Je continuerai donc à utiliser notre bon vieux sysV pour un moment.

jeudi 28 août 2014

Lambdas et capture d'attributs

J'ai aujourd'hui découvert que la capture d'attributs dans une lambda peut parfois donner des résultats surprenants. Regardez donc l'innocent programme que voilà. L'idée est d'avoir une classe A qui créé une lambda, laquelle capture un attribut de type pointeur partagé. L'on s'assure que A est ensuite détruit, avant d'exécuter la lambda. Que se passe-t-il?

#include <iostream>
#include <functional>
#include <memory>

class C
{
public:
  C():
    _value("abcdef")
  {
    std::cout << "Constructor" << std::endl;
  }

  ~C()
  {
    std::cout << "Destructor" << std::endl;
  }
  
  std::string _value;
};

class A
{
public:
  A():
    _c(std::make_shared<C>())
  {
  }
  
  std::function<void()> makeLambda()
  {
    return [=]()
    {
      std::cout << "Executing the lambda" << std::endl;
      std::cout << _c->_value << std::endl;      
    };
  }
  
  std::shared_ptr<C> _c;
};

int main()
{
  std::function<void()> f;

  {
    A a;
    f = a.makeLambda();
  }
  
  f();
  
  return 0;
}

Je pensais simplement que l'attribut allait être capturé par valeur (puisqu'il y a le "=" dans ma lambda), et que donc le pointeur partagé serait copié. Or, voilà ce qui se passe lorsque l'on exécute le programme:

Constructor
Destructor
Executing the lambda

L'objet C est détruit avant l'exécution de la lambda, c'est à dire au moment de la destruction de A! C'est en plein dans le royaume de l'undefined behaviour, en l'occurence ne rien afficher, mais plus probablement un crash. En cherchant un peu, je me suis rendu compte que les attributs sont en fait capturés via le pointeur this. D'ailleurs, la lambda refuse de compiler si l'on capture explicitement _c: [_c](){...};. En revanche, le compilateur est heureux en capturant this: [this](){...};.

La solution pour faire fonctionner l'exemple est tout simple: il faut copier _c dans une variable locale à la fonction, et capturer cette variable.

  std::function<void()> makeLambda()
  {
    auto localC = _c;
    return [=]()
    {
      std::cout << "Executing the lambda" << std::endl;
      std::cout << localC->_value << std::endl;      
    };
  }

Avec cette version, le résultat est celui attendu:

Constructor
Executing the lambda
abcdef
Destructor

Attention donc à ce qui est réellement capturé!

jeudi 21 août 2014

C++14 sort de sa boite

C'est fait! À l'unanimité, le nouveau standard C++14 a été approuvé, et sera rapidement officialisé. L'annonce est parue sur le blog de l'isocpp, et a fait l'objet de jolis trolls sur Slashdot. Le C++ continue, 30 ans après, de déchaîner les passions!

Quant à moi, j'espère bientôt voir apparaître dans nos compilateurs préférés les fonctionnalités de C++17, que je me ferai un plaisir de décortiquer ici. Restez branchés!

dimanche 3 août 2014

Passage de paramètres et move semantics - Planté!

J'avais posé la question sur StackOverflow, et je n'avais pas vraiment obtenu d'autre réponse que "il faut faire attention": avec la nouvelle manière de passer les paramètres qui seront copiés, en les passant par valeur puis en les déplaçant localement, il existait un grand risque d'utiliser par erreur le paramètre après son déplacement. Et c'est ce qui m'est arrivé il y a quelques jours, me coutant 2 bonnes heures de boulot. Je vous le fait en simplifié:

class A()
{
public:
  A(std::string instance):
    _instance(std::move(instance)),
    _logPrefix("Class A : " + instance)
  {
  }

private:
  std::string _instance;
  std::string _logPrefix;
}

Avec l'ancienne convention, instance aurait été passé comme une référence constante. Avec la nouvelle, l'on déplace instance dans _instance, mais après, il ne faut surtout pas l'utiliser! Une petite erreur d'inattention, et mon _logPrefix contient n'importe quoi, ce qui cassait tout. En l’occurrence, il contenait "Class A: ", mais cela aurait tout aussi bien pu être un beau crash.

Dommage. Je finis par me demander s'il ne faut pas garder l'ancienne convention, et n'utiliser la nouvelle que dans du code qui a vraiment besoin des performances et qui a une chance de se retrouver avec une l-value dans le paramètre.