Parcours
/
C++
C++
/
Exercices
/
La puissance de Troy
La puissance de Troy

La puissance de Troy

Exercice d'apprentissage

Introduction

Le mot-clé auto en C++

En C++, le mot-clé auto est une fonctionnalité puissante introduite en C++11, qui permet de déclarer des variables dont le type est déduit. Le compilateur déduit le type de la variable à partir de son initialiseur, ce qui peut rendre le code plus lisible et plus facile à maintenir.

Exemple d'utilisation

Prenons l'exemple suivant, où auto sert à déclarer des variables :

auto dragon_population{3};          // dragon_population is deduced as an integer
auto westeros{7.7777};              // westeros is deduced as a double
auto wedding_location{"The Twins"}; // wedding_location is deduced as a const char*, not std::string

Dans chaque cas, le type de la variable est déduit de la valeur avec laquelle elle est initialisée.

Inférence de type

Le mot-clé auto aide à écrire du code plus concis et plus lisible, en réduisant la verbosité des types explicites.

const std::vector<std::string> pigeon_pie{"flour", "butter", "pigeon", "salt"};
auto purple_wedding_pie{pigeon_pie};
purple_wedding_pie.emplace_back("the strangler");

Ici, auto déduit le type de purple_wedding_pie comme étant std::vector<std::string>, ce qui évite de devoir spécifier à nouveau le type explicitement.

Compatibilité

Le mot-clé auto est compatible avec diverses constructions du C++, ce qui en fait un outil polyvalent dans la programmation C++ moderne.

auto& element{array[0]};           // reference to an element
const auto object{otherObject};    // const type version of otherObject's type
auto* ptr{&x};                     // pointer to x with the same type as x, but as a pointer.

Dans les prochains concepts, on rencontrera souvent le mot-clé auto avec les expressions lambda, les boucles for basées sur des intervalles et les itérateurs.

Pointeurs intelligents

Les pointeurs intelligents sont une fonctionnalité moderne du C++ conçue pour assurer une gestion automatique de la mémoire, ce qui aide à éviter les fuites de mémoire et les pointeurs pendants souvent associés aux pointeurs bruts. Ils agissent comme des enveloppes autour des pointeurs bruts, en ajoutant des fonctionnalités supplémentaires, comme la libération automatique de la mémoire lorsque le pointeur n'est plus nécessaire.

Syntaxe générale

Les pointeurs intelligents sont généralement implémentés sous forme de gabarits de classe dans la bibliothèque standard du C++. Les deux pointeurs intelligents les plus couramment utilisés sont std::unique_ptr et std::shared_ptr.

Pointeurs uniques

std::unique_ptr est un pointeur intelligent qui possède l'objet de manière exclusive. Il garantit qu'à tout moment, un seul objet std::unique_ptr possède la ressource. Lorsque le std::unique_ptr propriétaire est détruit ou réinitialisé, il détruit automatiquement les objets et libère sa mémoire.

#include <memory>
// Declaring and defining a unique pointer
auto rightful_king_of_england = std::make_unique<std::string>("Excalibur");

// Unique pointers cannot be copied or assigned
auto mordred = rightful_king_of_england; // Error: Cannot copy a unique_ptr

Avantages de std::make_unique()

Lorsqu'on crée un std::unique_ptr, il est préférable d'utiliser std::make_unique() plutôt que d'allouer la mémoire directement avec new. std::make_unique() offre plusieurs avantages :

  1. Sécurité face aux exceptions : std::make_unique() garantit la sécurité face aux exceptions. Si une exception est levée pendant la construction de l'objet, la mémoire est automatiquement libérée, ce qui évite les fuites de mémoire.
  2. Clarté : utiliser std::make_unique() rend le code plus clair et plus concis. Cela élimine le besoin de spécifier explicitement le type alloué, car les arguments du gabarit sont déduits automatiquement.
  3. Opportunités d'optimisation : les compilateurs peuvent optimiser std::make_unique() plus efficacement qu'une allocation manuelle de mémoire avec new, ce qui peut améliorer les performances.
  4. Éviter les mauvais usages : il est possible de supprimer la ressource sous-jacente lorsqu'on construit le std::unique_ptr manuellement. Cela entraînerait un comportement indéfini lorsque le std::unique_ptr tente de la supprimer à la fin de sa portée.

Pointeurs partagés

std::shared_ptr est un pointeur intelligent qui permet à plusieurs objets std::shared_ptr de partager la propriété d'une même ressource. Il garde la trace du nombre de pointeurs partagés qui référencent la ressource, et ne libère la mémoire que lorsque le dernier pointeur partagé qui possède cette ressource sort de sa portée ou est réinitialisé.

// Declaring and defining a shared pointer to a dynamically allocated string
auto martian_congressional_republic = std::make_shared<std::string>("protomolecule");

// Creating more shared pointer that shares ownership
auto outer_planets_alliance = martian_congressional_republic;
auto united_nations = martian_congressional_republic;
Caution

En C++17 et antérieur, l'utilisation de std::shared_ptr avec des tableaux via std::make_shared<T[]> n'est pas directement prise en charge. Bien qu'il soit possible d'allouer des tableaux avec std::make_shared<T[]>, créer directement des pointeurs partagés à partir de ceux-ci peut entraîner un comportement indéfini, en raison des différences de gestion de la mémoire entre les objets seuls et les tableaux. À la place, envisage d'utiliser std::vector ou des fonctions de suppression personnalisées pour gérer efficacement des tableaux avec des pointeurs partagés. Vérifie toujours la compatibilité avec le compilateur et l'implémentation de la bibliothèque standard que tu utilises lorsque tu manipules des allocations de tableaux et des pointeurs partagés en C++17.

Avantages de std::make_shared()

Comme std::make_unique(), std::make_shared() offre des avantages tels qu'une meilleure efficacité mémoire, la sécurité face aux exceptions et la lisibilité. Il combine en une seule opération l'allocation de mémoire pour le bloc de contrôle et pour l'objet géré, ce qui améliore l'efficacité et réduit le risque de fuites de mémoire. De plus, la déduction automatique des arguments du gabarit simplifie le code et améliore sa lisibilité. Utiliser std::make_shared() favorise un code plus propre, plus sûr et plus efficace lorsqu'on travaille avec des objets std::shared_ptr en C++.

Conseils d'utilisation

Utilise par défaut des pointeurs intelligents : std::unique_ptr pour une propriété exclusive et std::shared_ptr pour une propriété partagée. Réserve les pointeurs bruts aux références non propriétaires ou à l'interfaçage avec du code hérité. Dans la plupart des cas, std::unique_ptr suffit pour une propriété exclusive, car il offre une gestion de mémoire légère, sans la surcharge du comptage de références. std::shared_ptr doit être utilisé avec parcimonie, car il introduit une surcharge et de la complexité, sauf si une véritable propriété partagée est nécessaire.

Instructions

Cet exercice t'emmène dans le monde de Troie. La vie de ses habitants est pleine de merveilles et de magie. De nombreux humains de Troie possèdent des pouvoirs, qu'ils utilisent fréquemment dans leur vie quotidienne. Les pouvoirs servent à remodeler le monde ou à influencer la faune de Troie et les autres personnes. La magie se manifeste aussi dans des artefacts uniques, très recherchés par les aventuriers, les artisans et les sages.

Dans cet exercice, tu vas écrire du code pour modéliser les humains de Troie, les artefacts qu'ils possèdent et les interactions entre les pouvoirs.

Tu as six tâches. La première porte sur la création d'un humain, les cinq autres sur la manipulation des pouvoirs et des artefacts.

1. Fais venir les humains dans le monde de Troie

Dans ton modèle de Troie, les humains sont l'élément le plus important. Ton humain modèle doit pouvoir posséder un artefact unique. Il doit aussi pouvoir manifester un pouvoir. Ces pouvoirs peuvent affecter d'autres humains, tu veux donc aussi modéliser le fait qu'un humain soit influencé par un autre pouvoir.

On te fournit des implémentations de base des structures artifact et power. Implémente une structure human (ou une classe) qui possède un smart-pointer vers une variable membre artifact nommée possession. Chaque artefact ne peut être possédé que par un seul humain à la fois.

Un human doit avoir deux variables membres supplémentaires. L'une contient son own_power et l'autre un pouvoir par lequel il est influencé (influenced_by). own_power et influenced_by sont tous deux des smart-pointers vers des powers. Chaque power peut être possédé par un seul humain, tout en influençant d'autres humains en même temps.

Par défaut, les humains naissent sans aucun artefact, ne possèdent aucun pouvoir et ne sont influencés par aucun d'eux.

human mindy_mccready{};
mindy_mccready.possession;
// => nullptr
mindy_mccready.own_power;
// => nullptr
mindy_mccready.influenced_by;
// => nullptr

2. Fais venir les artefacts à Troie

Ton modèle est ennuyeux sans l'interaction de ses parties. Tu veux créer des artefacts uniques et les donner à certains humains.

Définis la fonction give_new_artifact qui ne renvoie rien mais prend une référence vers un human et une string. À partir de cette string, elle doit définir un nouvel objet artifact et définir le pointeur possession du human en conséquence. La fonction ne doit rien renvoyer.

human erik_magnus_lehnsherr{};
give_new_artifact(erik_magnus_lehnsherr, "Mind shielding helmet");

erik_magnus_lehnsherr.possession->name;
// "Mind shielding helmet"

3. Rends les objets échangeables

Le monde de Troie est entièrement tourné vers l'interaction. Tu veux que les gens fassent des échanges en troquant leurs possessions.

Écris une fonction exchange_artifacts qui ne renvoie rien mais prend deux smart-pointers d'artefacts pour échanger les objets. Rappelle-toi qu'on ne peut pas copier un unique_ptr. Cela vaut aussi pour leur utilisation en tant que paramètres de fonction. Utilise plutôt une référence vers le unique_ptr.

human uchiha{};
give_new_artifact(uchiha, "konoha headband");
human uzumaki{};
give_new_artifact(uzumaki, "forehead protector");

exchange_artifacts(uchiha.possession, uzumaki.possession);

uchiha.possession->name;
// "forehead protector"
uzumaki.possession->name;
// "konoha headband"

4. Donne le pouvoir au peuple

L'aspect le plus fascinant de Troie réside dans les pouvoirs spéciaux que certaines personnes peuvent maîtriser. Certains peuvent fondre le fer par la pensée, tandis que d'autres peuvent guérir n'importe quelle blessure instantanément, la nuit.

Définis la fonction manifest_power qui ne renvoie rien mais prend une référence vers un human et une string. À partir de cette string, elle doit définir un nouvel objet power et définir le pointeur own_power du human en conséquence. La fonction ne doit rien renvoyer.

human eleven {};
manifest_power(eleven, "psychokinesis");

eleven.own_power->effect;
// "psychokinesis"

5. Utilise le pouvoir

À quoi servent les plus grands pouvoirs, si on ne peut pas les utiliser ? Ton modèle se concentre sur les humains, tu veux donc suivre l'influence des pouvoirs.

Écris une fonction void use_power qui prend deux références vers des humains. Le premier humain est le lanceur et le second représente la cible. Le pointeur influenced_by de la cible doit pointer vers le pouvoir du lanceur.

Par souci de simplicité, un humain ne peut être influencé que par un seul pouvoir. Ce pouvoir reste en place même si le lanceur n'existe plus.

human pamela_isley{};
manifest_power(pamela_isley, "control pheromones");

human count_vertigo{};
use_power(pamela_isley, count_vertigo);
count_vertigo.influenced_by->effect;
// "control pheromones"

6. Surveille l'intensité du pouvoir

Certains pouvoirs perdent de leur puissance ou déclenchent certains effets dans ta simulation lorsqu'ils sont appliqués à plusieurs humains. Tu veux suivre le nombre de personnes connectées à chaque pouvoir.

Définis la fonction power_intensity, qui prend un humain et renvoie l'intensité de son pouvoir sous forme d'int. Si la personne n'a pas de pouvoir, la valeur de retour doit être 0. Sinon, l'intensité doit refléter le lanceur et toutes les personnes actuellement influencées.

human jean_grey{};
manifest_power(jean_grey, "uplifting personality");

human scott{};
human logan{};
human ororo{};

use_power(jean_grey, ororo);
use_power(jean_grey, logan);
use_power(jean_grey, scott);

power_intensity(jean_grey);
// 4
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet
C++ Exercism

Prêt à commencer La puissance de Troy ?

Inscris-toi sur Exercism pour apprendre et maîtriser C++ avec 19 concepts100 exercices, et un vrai mentorat humain, le tout gratuitement.