auto in C++In C++, la parola chiave auto è una funzionalità potente introdotta in C++11, usata per dichiarare variabili con un tipo di dato inferito.
Il compilatore deduce il tipo della variabile dal suo inizializzatore, il che può rendere il codice più leggibile e più facile da mantenere.
Considera il seguente esempio, in cui auto viene usata per dichiarare delle variabili:
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
In ogni caso, il tipo della variabile viene inferito dal valore con cui è inizializzata.
La parola chiave auto aiuta a scrivere codice più conciso e leggibile, riducendo la verbosità dei tipi espliciti.
const std::vector<std::string> pigeon_pie{"flour", "butter", "pigeon", "salt"};
auto purple_wedding_pie{pigeon_pie};
purple_wedding_pie.emplace_back("the strangler");
In questo esempio, auto deduce il tipo di purple_wedding_pie come std::vector<std::string>, evitando di dover specificare di nuovo il tipo in modo esplicito.
La parola chiave auto è compatibile con vari costrutti del C++, il che la rende uno strumento versatile nella programmazione C++ moderna.
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.
Nei concetti successivi vedremo spesso la parola chiave auto con le espressioni lambda, i cicli for basati su intervallo e gli iteratori.
I puntatori intelligenti sono una funzionalità moderna del C++ pensata per offrire una gestione automatica della memoria, e aiutano a prevenire le perdite di memoria e i puntatori penzolanti, spesso associati ai puntatori grezzi. Fanno da involucro attorno ai puntatori grezzi, aggiungendo funzionalità extra, come la deallocazione automatica della memoria quando il puntatore non serve più.
I puntatori intelligenti sono in genere implementati come template di classe nella libreria standard del C++.
I due puntatori intelligenti più usati sono std::unique_ptr e std::shared_ptr.
std::unique_ptr è un puntatore intelligente che possiede l'oggetto in modo esclusivo.
Garantisce che in qualsiasi momento un solo oggetto std::unique_ptr possieda la risorsa.
Quando il std::unique_ptr proprietario viene distrutto o reimpostato, distrugge automaticamente gli oggetti e ne libera la memoria.
#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
std::make_unique()
Quando si crea uno std::unique_ptr, è preferibile usare std::make_unique() invece di ricorrere direttamente a new per allocare la memoria.
std::make_unique() offre diversi vantaggi:
std::make_unique() garantisce la sicurezza rispetto alle eccezioni.
Se durante la costruzione dell'oggetto viene lanciata un'eccezione, la memoria viene deallocata automaticamente, evitando perdite di memoria.std::make_unique() rende il codice più chiaro e più conciso.
Elimina la necessità di specificare esplicitamente il tipo da allocare, perché gli argomenti del template vengono dedotti automaticamente.std::make_unique() in modo più efficace rispetto all'allocazione manuale della memoria con new, con un possibile miglioramento delle prestazioni.std::unique_ptr viene costruito manualmente.
Questo porterebbe a un comportamento indefinito, quando lo std::unique_ptr prova a eliminarla alla fine del suo scope.std::shared_ptr è un puntatore intelligente che permette a più oggetti std::shared_ptr di condividere la proprietà della stessa risorsa.
Tiene traccia di quanti puntatori condivisi fanno riferimento alla risorsa e dealloca la memoria solo quando l'ultimo puntatore condiviso che possiede la risorsa esce dallo scope o viene reimpostato.
// 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;
In C++17 e versioni precedenti, usare std::shared_ptr con gli array tramite std::make_shared<T[]> non è supportato direttamente.
Sebbene sia possibile allocare array con std::make_shared<T[]>, creare da essi dei puntatori condivisi direttamente può portare a un comportamento indefinito, a causa delle differenze nella gestione della memoria tra oggetti singoli e array.
Al suo posto, valuta di usare std::vector o funzioni di cancellazione personalizzate per gestire gli array con i puntatori condivisi in modo efficace.
Assicurati sempre la compatibilità con il tuo compilatore e con l'implementazione della libreria standard quando hai a che fare con allocazioni di array e puntatori condivisi in C++17.
std::make_shared()
Come std::make_unique(), std::make_shared() offre vantaggi come una migliore efficienza della memoria, la sicurezza rispetto alle eccezioni e la leggibilità.
Combina in un'unica operazione l'allocazione della memoria per il blocco di controllo e per l'oggetto gestito, aumentando l'efficienza e riducendo il rischio di perdite di memoria.
Inoltre, la deduzione automatica degli argomenti del template semplifica il codice e ne migliora la leggibilità.
Usare std::make_shared() favorisce un codice più pulito, più sicuro e più efficiente quando si lavora con oggetti std::shared_ptr in C++.
Usa i puntatori intelligenti come impostazione predefinita: std::unique_ptr per la proprietà esclusiva e std::shared_ptr per la proprietà condivisa.
Riserva i puntatori grezzi ai riferimenti non proprietari o all'interfacciamento con codice legacy.
Nella maggior parte dei casi, std::unique_ptr è sufficiente per la proprietà esclusiva, perché offre una gestione della memoria leggera, senza il sovraccarico del conteggio dei riferimenti.
std::shared_ptr va usato con parsimonia, perché introduce sovraccarico e complessità, a meno che non serva una vera proprietà condivisa.
Questo esercizio ti porta nel mondo di Troia. La vita dei suoi abitanti è piena di meraviglia e magia. Molti umani a Troia possiedono dei poteri, che usano spesso nella vita quotidiana. I poteri servono a rimodellare il mondo o a influenzare la fauna di Troia e le altre persone. La magia si manifesta anche in artefatti unici, molto ricercati da avventurieri, artigiani e saggi.
In questo esercizio scriverai del codice per modellare gli umani di Troia, gli artefatti che possiedono e le interazioni tra i poteri.
Hai sei compiti. Il primo riguarda la creazione di un umano, gli altri cinque riguardano la gestione dei poteri e degli artefatti.
Nel tuo modello di Troia, gli umani sono l'elemento più importante. Il tuo modello di umano dovrebbe poter possedere un artefatto unico. Dovrebbe anche avere la capacità di manifestare un potere. Questi poteri possono influenzare altri umani, quindi vuoi anche modellare se un umano è influenzato da qualche altro potere.
Ti vengono fornite delle implementazioni di base delle struct artifact e power.
Implementa una struct human (o class) che abbia un puntatore intelligente a una variabile membro di tipo artifact chiamata possession.
Ogni artefatto può essere posseduto da un solo umano alla volta.
Un human deve avere due variabili membro aggiuntive.
Una contiene il proprio own_power, l'altra è il potere da cui è influenced_by.
Sia own_power che influenced_by sono puntatori intelligenti a powers.
Ogni power può essere posseduto da un solo umano, ma allo stesso tempo può anche influenzarne altri.
Per impostazione predefinita, gli umani nascono senza alcun artefatto e non possiedono alcun potere né ne sono influenzati.
human mindy_mccready{};
mindy_mccready.possession;
// => nullptr
mindy_mccready.own_power;
// => nullptr
mindy_mccready.influenced_by;
// => nullptr
Il tuo modello è noioso senza l'interazione tra le sue parti. Vuoi creare artefatti unici e darli a determinati umani.
Definisci la funzione give_new_artifact che non restituisce nulla ma prende un riferimento a un human e una string.
Con la string dovrebbe definire un nuovo oggetto artifact e impostare di conseguenza il puntatore possession dell'human.
La funzione non dovrebbe restituire nulla.
human erik_magnus_lehnsherr{};
give_new_artifact(erik_magnus_lehnsherr, "Mind shielding helmet");
erik_magnus_lehnsherr.possession->name;
// "Mind shielding helmet"
Il mondo di Troia ruota tutto attorno all'interazione. Vuoi che le persone facciano degli scambi, cedendosi ciò che possiedono.
Scrivi una funzione exchange_artifacts che non restituisce nulla ma prende due puntatori intelligenti ad artefatti per scambiare gli oggetti.
Ricorda che non puoi copiare un unique_ptr.
Questo vale anche per l'uso nei parametri di funzione.
Usa invece un riferimento al 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"
L'aspetto più entusiasmante di Troia sono i poteri speciali che le persone possono padroneggiare. Alcuni possono fondere il ferro con il pensiero, mentre altri possono guarire all'istante ogni ferita durante la notte.
Definisci la funzione manifest_power che non restituisce nulla ma prende un riferimento a un human e una string.
Con la string dovrebbe definire un nuovo oggetto power e impostare di conseguenza il puntatore own_power dell'human.
La funzione non dovrebbe restituire nulla.
human eleven {};
manifest_power(eleven, "psychokinesis");
eleven.own_power->effect;
// "psychokinesis"
A cosa servono i poteri più grandi, se non puoi usarli? Il tuo modello si concentra sugli umani, quindi vuoi tenere traccia dell'influenza dei poteri.
Scrivi una funzione void use_power che prende due riferimenti a umani.
Il primo umano è il lanciatore e il secondo rappresenta il bersaglio.
Il puntatore influenced_by del bersaglio dovrebbe puntare al potere del lanciatore.
Per semplicità, un umano può essere influenzato da un solo potere. Questo potere resta in vigore anche se il lanciatore non esiste più.
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"
Alcuni poteri perdono efficacia o innescano determinati effetti nella tua simulazione quando vengono applicati a più umani. Vuoi tenere traccia del numero di persone collegate a ciascun potere.
Definisci la funzione power_intensity, che prende un umano e restituisce l'intensità del suo potere come int.
Se la persona non ha alcun potere, il valore restituito dovrebbe essere 0.
Altrimenti l'intensità dovrebbe riflettere il lanciatore e tutte le persone attualmente influenzate.
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
Iscriviti a Exercism per imparare e padroneggiare C++ con 19 concetti100 esercizi e il mentoring di persone reali, tutto gratis.