-
-
Notifications
You must be signed in to change notification settings - Fork 217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add concept ex for smart pointers #846
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# Hints | ||
|
||
## 1. Bring humans to the world of Troy | ||
|
||
- The core of this exercise is the usage of `unique_ptr` and `shared_prt`. | ||
Which kind of smart pointer should be used for each of the `human` variables? | ||
- `artifacts` are not shared, but `powers` are needed to track influenced people. | ||
- the `possession` pointer should be unique, the power pointers should be shared. | ||
|
||
## 2. Bring Artifacts into Troy | ||
|
||
- You need to create a smart pointer and assign it to the `possession` variable | ||
- Use _references_ to change the object outside of the function. | ||
- Look up [`std::make_unique`][make_unique] for some hints. | ||
- The type of the pointer's target object has to be put in the angled brackets and again inside the parens to define the object. | ||
- Here is a full example: `variable = std::make_unique<int>(int{23});` | ||
|
||
## 3. Make items tradeable | ||
|
||
- You can look through the [unique_ptr reference][unique_ptr] to find a fitting function. | ||
- Do you think `std::swap` can help you? | ||
|
||
## 4. Give Power to the People | ||
|
||
- You need to create a smart pointer and assign it to the `own_power` variable | ||
- Use _references_ to change the object outside of the function. | ||
- Look up [`std::make_shared`][make_shared] for some hints. | ||
- The type of the pointer's target object has to be put in the angled brackets and again inside the parens to define the object. | ||
- Here is a full example: `variable = std::make_shared<int>(int{23});` | ||
|
||
## 5. Use the Power | ||
|
||
- Use _references_ to change the object outside of the function. | ||
|
||
## 6. Keep watch on the power's intensity | ||
|
||
- You can look through the [shared_ptr reference][shared_ptr] to find a fitting function. | ||
- Do you think `use_count` can help you? | ||
|
||
[unique_ptr]: https://en.cppreference.com/w/cpp/memory/unique_ptr | ||
[make_unique]: https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique | ||
[shared_ptr]: https://en.cppreference.com/w/cpp/memory/shared_ptr | ||
[make_shared]: https://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
# Instructions | ||
|
||
This exercise takes you to the world of Troy. | ||
The lives of its people are full of wonder and magic. | ||
Many humans in Troy possess _powers_, that are used frequently in their daily lives. | ||
Powers are used to re-shape the world or influence Troy's fauna and other people. | ||
Magic also manifests in _unique artifacts_, that are highly sought after by adventurers, artisans and sages. | ||
|
||
In this exercise you are going to write code to model the humans of Troy, their possessed artifacts and power interactions. | ||
|
||
You have six tasks. | ||
The first one is related to creating a human, the other five are about handling powers and artifacts. | ||
|
||
## 1. Bring humans to the world of Troy | ||
|
||
For your model of Troy humans are the most important feature. | ||
Your model human should be able to possess a _unique artifact_. | ||
They should also have the ability to manifest a _power_. | ||
These powers might affect other humans, so you also want to model if a human is influenced by some other power. | ||
|
||
You are provided with basic implementations of `artifact` and `power` structs. | ||
Implement a `human` struct (or class) that has a _smart-pointer_ to an `artifact` as `possession` member variable. | ||
Each artifact can only be possessed by a single human at any given time. | ||
|
||
The `human` should also have variables for their `own_power` and `influenced_by`, which should be _smart-pointers_ to `powers`. | ||
Each `power` might be owned by a single human, but also influence other humans at the same time. | ||
|
||
By default, humans are born without any artifact and neither own any powers nor are they influenced by them. | ||
|
||
```cpp | ||
human mindy_mccready{}; | ||
mindy_mccready.possession; | ||
// => nullptr | ||
mindy_mccready.own_power; | ||
// => nullptr | ||
mindy_mccready.influenced_by; | ||
// => nullptr | ||
``` | ||
|
||
## 2. Bring Artifacts into Troy | ||
|
||
Your model is boring without the interaction of its parts. | ||
You want to create unique artifacts and give them to certain humans. | ||
|
||
Define the function `give_new_artifact` which returns nothing but takes a `human` and a `string`. | ||
With the `string` it should define a new `artifact` object and set the `possession` pointer of the `human` accordingly. | ||
The function should not return anything. | ||
|
||
```cpp | ||
human erik_magnus_lehnsherr{}; | ||
give_new_artifact(erik_magnus_lehnsherr, "Mind shielding helmet"); | ||
|
||
erik_magnus_lehnsherr.possession->name; | ||
// "Mind shielding helmet" | ||
``` | ||
|
||
## 3. Make items tradeable | ||
|
||
The world of Troy is all about interaction. | ||
You want people to make trades by exchanging their possessions. | ||
|
||
Write a function `exchange_artifacts` that returns nothing but takes two artifact smart-pointers to exchange the items. | ||
|
||
```cpp | ||
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. Give Power to the People | ||
|
||
The most exiting feature of Troy are the special powers, that people might wield. | ||
Some can smelt iron with their thoughts, while others can heal every wound instantly at nighttime. | ||
|
||
Define the function `manifest_power` which returns nothing but takes a `human` and a `string`. | ||
With the `string` it should define a new `power` object and set the `own_power` pointer of the `human` accordingly. | ||
The function should not return anything. | ||
|
||
```cpp | ||
human eleven {}; | ||
manifest_power(eleven, "psychokinesis"); | ||
|
||
eleven.own_power->effect; | ||
// "psychokinesis" | ||
``` | ||
|
||
## 5. Use the Power | ||
|
||
What use are the greatest powers, if you cannot use them. | ||
Your model concentrates on humans, so you want to track the influence of powers. | ||
|
||
Write a _void_ function `use_power` that takes two humans, first: a caster and secondly: a target. | ||
The target's `influenced_by` pointer should be pointed to the power of the caster. | ||
|
||
For simplicity, humans can only be influenced by a single power and this power stays in place even if the caster does not exist any longer. | ||
|
||
```cpp | ||
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. Keep watch on the power's intensity | ||
|
||
Certain powers lose their potency or trigger certain effects in your simulation when they are applied to several humans. | ||
You want to track the number of people who are connected to each power. | ||
|
||
Define the function `power_intensity`, that takes a human and returns the intensity of their power as an _int_. | ||
If the person has no power, the return value should be `0`. | ||
Otherwise the intensity should reflect the caster and all currently influenced people. | ||
|
||
```cpp | ||
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 | ||
``` |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,98 @@ | ||||
# Introduction | ||||
|
||||
Smart pointers are a modern C++ feature designed to provide automatic memory management, helping to prevent memory leaks and dangling pointers commonly associated with raw pointers. | ||||
They act as wrappers around raw pointers, adding additional functionality such as automatic memory deallocation when the pointer is no longer needed. | ||||
|
||||
## General Syntax | ||||
|
||||
Smart pointers are typically implemented as class templates in the C++ standard library. | ||||
The two most commonly used smart pointers are `std::unique_ptr` and `std::shared_ptr`. | ||||
|
||||
## Unique Pointers | ||||
|
||||
|
||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||
`std::unique_ptr` is a smart pointer that owns the object exclusively. | ||||
It ensures that at any given time, only one `std::unique_ptr` object owns the resource. | ||||
When the owning `std::unique_ptr` is destroyed or reset, it automatically destructs the objects and releases its memory. | ||||
|
||||
```cpp | ||||
#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 | ||||
``` | ||||
|
||||
## Advantages of `std::make_unique()` | ||||
|
||||
When creating a `std::unique_ptr`, it's preferable to use `std::make_unique()` instead of directly using `new` to allocate memory. | ||||
`std::make_unique()` provides several advantages: | ||||
1. **Exception Safety**: `std::make_unique()` guarantees exception safety. | ||||
If an exception is thrown during the construction of the object, memory will be automatically deallocated, preventing memory leaks. | ||||
2. **Clarity**: Using `std::make_unique()` makes code clearer and more concise. | ||||
It eliminates the need to explicitly specify the type being allocated, as the template arguments are deduced automatically. | ||||
3. **Optimization Opportunities**: Compilers have the opportunity to optimize `std::make_unique()` more effectively than manually allocating memory with `new`, potentially resulting in improved performance. | ||||
4. **Avoiding Misuse**: Deleting the underlying resource is possible, when the `std::unique_ptr` is constructed manually. | ||||
That would lead to undefined behavior, when the `std::unique_ptr` tries to delete it at its end of scope. | ||||
|
||||
## Shared Pointers | ||||
|
||||
`std::shared_ptr` is a smart pointer that allows multiple `std::shared_ptr` objects to share ownership of the same resource. | ||||
It keeps track of how many shared pointers are referencing the resource, and deallocates the memory only when the last shared pointer owning the resource goes out of scope or is reset. | ||||
|
||||
```cpp | ||||
// 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; | ||||
``` | ||||
|
||||
~~~~exercism/caution | ||||
In C++17 and below, using `std::shared_ptr` with arrays via `std::make_shared<T[]>` is not directly supported. | ||||
While it's possible to allocate arrays with `std::make_shared<T[]>`, creating shared pointers directly from them may lead to undefined behavior due to differences in memory management between single objects and arrays. | ||||
Instead, consider using `std::vector` or custom deletion functions to manage arrays with shared pointers effectively. | ||||
Always ensure compatibility with your compiler and standard library implementation when dealing with array allocations and shared pointers in C++17. | ||||
~~~~ | ||||
|
||||
## Advantages of `std::make_shared()` | ||||
|
||||
Similar to `std::make_unique()`, `std::make_shared()` offers benefits such as improved memory efficiency, exception safety, and readability. | ||||
It combines memory allocation for the control block and the managed object into a single operation, enhancing efficiency and reducing the risk of memory leaks. | ||||
Additionally, automatic deduction of template arguments simplifies code and enhances readability. | ||||
Using `std::make_shared()` promotes cleaner, safer, and more efficient code when working with `std::shared_ptr` objects in C++. | ||||
|
||||
|
||||
~~~~exercism/advanced | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should I take this out, even though it is in the connected concept? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are students required to use |
||||
## Weak Pointers | ||||
|
||||
`std::weak_ptr` is a companion class to `std::shared_ptr` that provides a non-owning "weak" reference to an object managed by a shared pointer. | ||||
|
||||
```cpp | ||||
// Creating a shared pointer | ||||
auto your_account = std::make_shared<std::string>("secret_subscription_password"); | ||||
// Creating a shared pointer that shares ownership | ||||
auto your_flatmates_account = your_account; | ||||
|
||||
// Creating a weak pointer from the shared pointer | ||||
auto your_flatmates_boyfriends_account = your_flatmates_account; | ||||
// if your_account and your_flatmates_account are deleted, there is no more reference to the shared pointer. | ||||
// your_flatmates_boyfriends_account will be a null pointer and cannot use the associated object any longer. | ||||
``` | ||||
|
||||
Weak pointers are useful in scenarios where cyclic references need to be broken to prevent memory leaks. | ||||
`std::weak_ptr` was designed to address the issue of cyclic ownership, also known as circular references, that can occur when using `std::shared_ptr`. | ||||
In a cyclic ownership scenario, two or more `std::shared_ptr` objects are referencing each other, creating a cycle where none of the objects can be deleted because they have strong references to each other, leading to memory leaks. | ||||
`std::weak_ptr` provides a solution to this problem by allowing weak references to shared objects without contributing to their reference count. | ||||
This means that it can observe and access the shared object but doesn't prevent it from being deleted. | ||||
~~~~ | ||||
|
||||
## Usage advice | ||||
|
||||
Use smart pointers by default: `std::unique_ptr` for exclusive ownership and `std::shared_ptr` for shared ownership. | ||||
Reserve raw pointers for non-owning references or when interfacing with legacy code. | ||||
In most cases, `std::unique_ptr` is sufficient for exclusive ownership, as it offers lightweight memory management without the overhead of reference counting. | ||||
`std::shared_ptr` should be used sparingly, as it introduces overhead and complexity unless true shared ownership is needed. | ||||
`std::weak_ptr` is specialized for breaking cyclic dependencies or observing shared objects, but it's not commonly used. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"authors": [ | ||
"vaeng" | ||
], | ||
"files": { | ||
"solution": [ | ||
"power_of_troy.cpp", | ||
"power_of_troy.h" | ||
], | ||
"test": [ | ||
"power_of_troy_test.cpp" | ||
], | ||
"exemplar": [ | ||
".meta/exemplar.cpp", | ||
".meta/exemplar.h" | ||
] | ||
}, | ||
"blurb": "Learn about smart pointers by using magic and artifacts in the world of Troy." | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# Design | ||
|
||
## Learning objectives | ||
|
||
- Know what a unique_ptr is | ||
- Know what a shared_ptr is | ||
|
||
## Out of scope | ||
|
||
- weak_ptr | ||
|
||
## Concepts | ||
|
||
- `smart-pointers` | ||
|
||
## Prerequisites | ||
|
||
- `pointers` | ||
|
||
## Analyzer | ||
|
||
- - |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
#include "power_of_troy.h" | ||
|
||
namespace troy { | ||
|
||
void give_new_artifact(human& receiver, std::string item_name) { | ||
receiver.possession = | ||
std::make_unique<artifact>(artifact{item_name}); // Include hint! | ||
} | ||
|
||
void exchange_artifacts(std::unique_ptr<artifact>& item_a, std::unique_ptr<artifact>& item_b) { | ||
std::swap(item_a, item_b); | ||
} | ||
|
||
void manifest_power(human& receiver, std::string power_effect) { | ||
receiver.own_power = std::make_shared<power>(power{power_effect}); | ||
} | ||
|
||
void use_power(const human& caster, human& receiver) { | ||
receiver.influenced_by = caster.own_power; | ||
} | ||
|
||
int power_intensity(const human& caster) { | ||
return caster.own_power.use_count(); | ||
} | ||
|
||
} // namespace troy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is well written summary!