samedi 24 mai 2014

make_unique

Le support C++14 de la bibliothèque standard s'améliore également, avec l'ajout de la fonction std::make_unique. Conceptuellement, c'est difficile de faire plus simple: c'est l'équivalent de std::make_shared, mais qui retourne un pointeur unique au lieu d'un pointeur partagé.

#include <memory>
#include <string>
#include <iostream>

int main()
{
  auto val = std::make_unique<std::string>("abcd");
  std::cout << *val << std::endl;
  return 0;
}

Si vous vous souvenez, l'intérêt de make_shared est double: d'une part, permettre l'allocation du bloc de contrôle (contenant entre autres le compteur de références) en même temps que les données, ce qui permet d'augmenter les performances à l'allocation et de réduire la fragmentation mémoire, et d'autre part de rendre le code exception safe dans ce genre de cas:

f(std::shared_ptr<A>(new A), std::shared_ptr<B>(new B)

L'ordre d'exécution des différents éléments n'est pas garanti, et il est possible que le compilateur alloue A, puis B, puis construise les shared_ptr. Dans ce cas, une exception dans le constructeur de B causerait une fuite mémoire dans A. L'utilisation de make_shared résout le problème:

f(std::make_shared<A>(), std::make_shared<B>()

Avec make_unique, la première raison ne tient plus, puisqu'il n'y a pas de structure de contrôle. La deuxième raison, bien sûr, tient encore.

Mais plus généralement, le couple std::make_shared / std::make_unique permet un nouvel idiome qui vise à se débarrasser de tous les new et de tous les delete lorsqu'ils correspondent à de la mémoire gérée par nos pointeurs intelligents. Ainsi, la présence d'un new est une indication pour le programmeur qui relit le code que la section gère la mémoire d'une manière particulière (par exemple, implémentation d'un conteneur bas niveau).

jeudi 22 mai 2014

Séparateurs de chiffres

Je continue sur les fonctionnalités C++1y de g++4.9, avec le moyennement utile séparateur de chiffres. Un petit programme valant mieux que de long discours, voici la bête:

#include <iostream>

int main()
{
  std::cout << 123'456 << std::endl;
  std::cout << 1234'56 << std::endl;
  std::cout << 12345'6 << std::endl;
  std::cout << 1'23'4.5'6 << std::endl;

  return 0;
}

L'exécution donne, comme prévu:

123456
123456
123456
1234.56

L'idée est donc d'avoir la possibilité d'ajouter l'apostrophe comme séparateur de chiffres, mais pas seulement nécessairement pour les milliers: on peut les mettre n'importe où. Utile, je suppose, pour les cultures qui groupent plutôt par dizaines de milliers.

C'est très mostly harmless, même si ça alourdit un poil le standard et l'implémentation... Pour améliorer la lisibilité des constantes, pourquoi ne pas compter plutôt sur l'éditeur de texte?

lundi 5 mai 2014

Lambdas polymorphiques

G++4.9 vient de débarquer dans Debian Jessie. C'est donc l'occasion de voir ce que cette nouvelle mouture nous réserve, en particulier du côté du support de C++14. Les lambdas polymorphiques sont similaires aux fonctions templates, les paramètres génériques (spécifiés par auto) étant résolus à l'instanciation de la fonction. Voici un petit exemple:

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>


int main()
{
  std::vector<int> v = {1, 2, 3, 4, 5};
  
  std::for_each(v.begin(),
                v.end(),
                [](auto i) { std::cout << i << std::endl; }
                );
  
  auto output = std::accumulate(
                  v.begin(),
                  v.end(),
                  std::string(" == > "),
                  [](auto & acc, auto i) 
                    { return acc + std::to_string(i); }
                  );

  std::cout << output << std::endl;
  
  return 0;
}

Dans l'exemple précédent, la fonction lambda est instanciée au moment où elle est déclarée, mais l'on n'est pas obligé d'effectuer ces opérations ensemble, si l'on veut réutiliser sa lambda. Voyez l'exemple suivant: la lambda est créée (son type est auto, puisqu'il n'est pas possible de déclarer une lambda autrement qu'en la définissant), et elle est utilisée (et donc instanciée) à deux endroits.

#include <iostream>
#include <vector>
#include <algorithm>


int main()
{
  auto turn_to_pair = [](auto & left, auto & right) 
    { return std::make_pair(left, right); };
  
  auto l1 = {1, 2, 3, 4, 5};
  auto l2 = {"a", "b", "c", "d", "e"};

  std::vector<std::pair<int, std::string> > r1;
  std::vector<std::pair<std::string, int> > r2;

  std::transform(l1.begin(),
                 l1.end(),
                 l2.begin(),
                 std::back_inserter(r1),
                 turn_to_pair);

  std::transform(l2.begin(),
                 l2.end(),
                 l1.begin(),
                 std::back_inserter(r2),
                 turn_to_pair);

  return 0;
}

Au passage, si j'ai bien tout compris au post de Scott Meyers, ma lambda pourrait être encore plus générique en prenant des références universelles. Après avoir longuement cherché, je n'ai toujours pas trouvé s'il était nécessaire d'utiliser le perfect forwarding si la fonction appelée attend elle-même des références universelles, mais ça ne peut pas faire de mal. L'on écrirait alors:

  auto turn_to_pair = [](auto && left, auto && right) 
    { return std::make_pair(std::forward(left), 
                            std::forward(right)); };