On continue l’aventure Cplusplus11 après le premier article qui était plus une mise ne jambe. Cette fois-ci nous allons attaquer les fonctions anonymes, Lambda expression. Les expressions lambda sont utiles pour écrire des blocs de code en ligne qui sont des fonctions un peu spéciales. Ces fonctions anonymes peuvent être passées en paramètre d’autres fonctions et peuvent aussi récupérer le contexte comme nous le verrons plus loin. Par ailleurs les fonctions lambda améliorent aussi la lisibilité des programmes C++11 car il n’est plus nécessaire d’encapsuler des méthodes qu’on veut utiliser qu’une fois dans des classes ou des fichiers externes. C’est maintenant l’heure d’explorer la signature des Lambda expression.

Signature d’une expression lambda

[capture] (arguments) attribute-specifier mutable exception-specification -> return-type {body}
// or
[capture] (arguments) -> return-type {body}
// or
[capture] (arguments) {body}
// or
[capture] {body}

Description de la signature de l’expression lambda

capture (liste de capture)
  • [] : aucune capture des variables du contexte
  • [this] : capture le pointeur this de la classe englobante
  • [&] : capture toutes les variables du contexte par référence
  • [=] : capture toutes les variables du contexte par valeur
  • [&x] : capture uniquement la variable x par référence
  • [x] : capture uniquement la variable x par par valeur
  • [x, &y] : capture la variable x par par valeur et y par référence
arguments (liste d’arguments)
  • liste des paramètres de la fonction lambda
attribute-specifier, mutable, exception-specification (modificateur et spécificateurs)
  • attribute-specifier : [[noreturn]] indique que la fonction ne retourne rien et [[carries_dependency]] permet de contrôler la gestion du stockage en mémoire. Ne fonctionne pas avec G++ 4.6.3, actuellement seul Intel-C++ 12.1 les supportes
  • mutable : permet de modifier les variables capturées par valeur dans le corps de la fonction
  • exception-specification : spécificateur d’exception tel que noexcept, throw(), etc…
return-type
Type de retour de la fonction lambda.
body
Corps de la fonction lambda.

Exemple d’utilisation (projet github des exemples)

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

int main() {
	std::vector<std::string> data{"first", "second", "third"};
	std::string one = "one!!";
	std::string two = "two";
	std::string three = "three";
	std::cout << std::endl;
	// Process data vector with lambda function
	for_each(begin(data), end(data), [one, &two, &three](std::string &item) mutable {
		item  += "_append";
		one   += "_another value now"; // We use "mutable" for variable passed by copy.
		two   += "_two";
		three += "_three";
	});
	// Print data vector after processing
	std::cout << "after:" << std::endl << " - data: ";
	for (const auto d : data) {
		std::cout << d << " ";
	}
	std::cout << std::endl;
	std::cout << " - one: " << one << " - two: " << two << " - three: " << three;
	std::cout << std::endl;
	return 0;
}

Dans cet exemple nous initialisons un vecteur de string avec 3 éléments, puis nous initialisons 3 variables de type string. Nous utilisons for_each pour parcourir le vecteur data et concaténons chaque élément avec une expression lambda passée en paramètre de for_each. Dans le corps de la fonction lambda, nous concaténons aussi les variables one (récupéré par valeur), two (récupéré par référence), three (récupéré par référence). Nous affichons, au final, le résultat. Vous remarquerez que la variable one n’a pas été impactée après le traitement car elle a été capturée par valeur.

//output
after:
 - data: first_append second_append third_append
 - one: one!! - two: two_two_two_two - three: three_three_three_three

Ce sera tout pour le moment, nous attaquerons d’autres nouveautés dans un prochain article…

Il y a quelques mois je me réjouissais de la validation du standard C++11. Je regrette toujours que les sockets réseau n’aient pas été implémenté.

Après plusieurs lectures sur le sujet, je recherche l’adoption totale de la nouvelle syntaxe de C++11. Tentons de dompter les nouveautés. Les exemples sont testés avec GCC version 4.6.1 (Ubuntu/Linaro 4.6.1-9ubuntu3), le code source est disponible sur GitHub.

Nous allons commencer à travailler sur les types automatiques avec le mot clé auto et l’opérateur decltype.

#include<iostream>
#include<vector>
#include<string>
#include<algorithm>//for_each
#include<typeinfo>//typeid

int main() {
  std::vector<std::string> data{"first", "second", "third"};//uniform brace notation
  for (const auto item : data) {//Range based for loop
  std::cout << "Iterate =>" << std::endl;
    std::cout << "  Automatic item type: " << typeid(item).name() << " - Item value: " << item << std::endl;
    decltype(item) dynamic_value = item;
    std::cout << "  Dynamic type: " << typeid(dynamic_value).name() << " - Dynamic value: " << dynamic_value << std::endl;
    std::cout << std::endl;
  }
  return 0;
}

Dans cet exemple nous initialisons un vecteur de chaîne de caractères puis bouclons sur chaque item ; le type de chaque item est récupéré automatiquement et nous créons dynamiquement une variable du même type que l’item. Pour le moment ne tenez pas compte de la syntaxe particulière de la boucle for.

Simplification de l’itération simple d’un conteneur avec la structure de contrôle for.

#include<iostream>
#include<vector>
#include<string>

int main() {
  std::vector<std::string> data{"first", "second", "third"};
  for(const auto item : data) {
    std::cout << "Item value: " << item << std::endl;
  }  
  return 0;
}

Une simple boucle for tel que le propose déjà des langages tel que Java ou C#. Un vrai bonheur pour notre langage. Notons que pour un parcours spécifique ou plus complexe, il est plus puissant d’utiliser for_each ; par exemple commencer l’itération à partir du 3ème élément.

Avant C++11, la macro NULL équivalente à 0, est parfois considéré comme très particulière par les développeurs et porte à confusion. C++11 propose une expression nullptr, plus d’excuses et arrêtons d’utiliser 0 ou NULL pour initialiser un pointeur.

#include<iostream>
#include<vector>

int main() {
  const int* p = nullptr;
  std::vector<int> data{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  for (const auto item : data) {
    p = &item;
    std::cout << "Item pointer: " << *p << std::endl;
  }
  return 0;
}

On initialise un pointeur avec nullptr et non 0, puis on boucle sur les données en assignant l’adresse de chaque item au pointeur p.

Uniformisation de l’initialisation avec les accolades {}. Avant C++11, selon qu’on veut initialiser un tableau, les membres d’une structures… avec des zéros ou avec des valeurs différentes de zéro, il fallait passer par des syntaxes différentes. Avec C++11 on arrête avec toutes ces possibilités, on uni formalise comme le montre le code source suivant :

#include<string>
#include<sstream>
#include<vector>

struct Version {
  uint32_t major;
  uint32_t minor;
  uint32_t revision;
  
  std::string toString() const {
    std::ostringstream oss;
    oss << major << "." << minor << "." << revision;
    return oss.str();
  }
};

int main() {
  Version v{0, 1, 0};//Constructor initialization
  std::cout << "Version v{0, 1, 0}: " << std::endl;
  std::cout << "  " << v.toString() << std::endl;
  std::vector<Version> vArray{{0, 1, 0}, {0, 1, 1}, {0, 1, 2}};//Array initialization
  std::cout << "std::vector<Version> vArray{{0, 1, 0}, {0, 1, 1}, {0, 1, 2}}: " << std::endl;
  for (const auto v : vArray) {
    std::cout << "  " << v.toString() << std::endl;
  }
  return 0;
}

On crée une instance de Version en initialisant major, minor et revision. On crée ensuite un vecteur de Version en initialisant les 3 premiers éléments.

Les nombres aléatoires, peuvent être générés selon un nouveau modèle qui se compose de deux éléments, un moteur de génération de nombre (il y en a plusieurs au choix) et une distribution soit une plage de nombre pouvant être généré, là encore on a le choix de la distribution ; voir un aperçu via ce lien.

#include<iostream>
#include<random>//std::mt19937, std::uniform_int_distribution
#include<functional>//std::bind
#include<ctime>

std::mt19937 engine;
std::uniform_int_distribution<uint32_t> distribution{1, 10};

int main() {
  engine.seed(static_cast<uint32_t>(std::time(nullptr)));
  auto generator = std::bind(distribution, engine);
  uint32_t random = generator();
  std::cout << "1st random: " << random << std::endl;
  random = generator();
  std::cout << "2nd random: " << random << std::endl;
  return 0;
}

Dans cet exemple on instancie le moteur de génération de type Mersenne Twister avec une distribution uniforme d’entier. On initialise dans la méthode main, le moteur avec un nombre entier récupérer de l’horloge de la machine. On se fabrique une fonction generator qui permet d’appeler notre générateur avec un nom plus intuitif.

Voilà c’est une première mise en jambe avec C++11 histoire de s’échauffer. Je reste persuadé du potentiel de ce langage et de son avenir glorieux. C++11 est le langage le plus concis et rapide de la planète.