adding smart pointer examples

This commit is contained in:
Jidong Xiao
2025-04-17 03:07:47 -04:00
committed by JamesFlare1212
parent fd6ef6545e
commit 418411d62d

View File

@@ -168,126 +168,160 @@ stack:
## 27.11 Practical Garbage Collection Methodology in C++: Smart Pointers ## 27.11 Practical Garbage Collection Methodology in C++: Smart Pointers
Garbage collection looks like an attractive option both when we are quickly drafting a prototype system and - Garbage collection looks like an attractive option both when we are quickly drafting a prototype system and
also when we are developing big complex programs that process and rearrange lots of data. also when we are developing big complex programs that process and rearrange lots of data.
Unfortunately, general-purpose, invisible garbage collection isnt something we can just tack onto C++, an - Unfortunately, general-purpose, invisible garbage collection isnt something we can just tack onto C++, an
enormous beast of a programming language (but that doesnt stop people from trying!). So is there anything enormous beast of a programming language (but that doesnt stop people from trying!). So is there anything
we can do? Yes, we can use Smart Pointers to gain some of the features of garbage collection. we can do? Yes, we can use Smart Pointers to gain some of the features of garbage collection.
Some examples below are modified from these nice online references:
[http://ootips.org/yonat/4dev/smart-pointers.html](http://ootips.org/yonat/4dev/smart-pointers.html) ## 27.12 C++ Smart Pointers
[http://www.codeproject.com/KB/stl/boostsmartptr.aspx](http://www.codeproject.com/KB/stl/boostsmartptr.aspx) - Smart pointers in C++ are objects that manage the lifetime of dynamically allocated memory, ensuring proper deallocation and preventing memory leaks.
[http://en.wikipedia.org/wiki/Smart_pointer](http://en.wikipedia.org/wiki/Smart_pointer) - Available since C++11 in the <memory> header.
[http://www.boost.org/doc/libs/1_48_0/libs/smart_ptr/smart_ptr.htm](http://www.boost.org/doc/libs/1_48_0/libs/smart_ptr/smart_ptr.htm) ## 27.13 Why Smart Pointers
[http://www.acodersjourney.com/2016/05/top-10-dumb-mistakes-avoid-c-11-smart-pointers/](http://www.acodersjourney.com/2016/05/top-10-dumb-mistakes-avoid-c-11-smart-pointers/) - Traditional pointers require manual memory management:
## 27.12 Whats a Smart Pointer?
- The goal is to create a widget that works just like a regular pointer most of the time, except at the beginning
and end of its lifetime. The syntax of how we construct smart pointers is a bit different and we dont need to
obsess about how & when it will get deleted (it happens automatically).
- Heres one flavor of a smart pointer (simplified from STL):
```cpp ```cpp
template <class T> int* ptr = new int(5);
class auto_ptr { // ...
delete ptr;
```
If not handled correctly, traditional pointers can lead to:
- Memory leaks
- Dangling pointers
- Double deletions
Smart pointers automate memory management and ensure exception safety and ownership clarity.
## 27.14 Types of Smart Pointers
### 27.14.1 std::unique_ptr
- Exclusive ownership of a dynamically allocated object.
- Cannot be copied, only moved.
- Deletes the object when it goes out of scope.
- Use Cases:
- When only one owner should exist.
- Performance-sensitive code (no reference count overhead).
```cpp
#include <memory>
std::unique_ptr<int> ptr = std::make_unique<int>(10);
```
- Move Ownership:
```cpp
std::unique_ptr<int> ptr2 = std::move(ptr); // ptr is now null
```
Below is an std::unique_ptr example program:
```cpp
#include <iostream>
#include <memory>
#include <string>
class Girlfriend {
public: public:
explicit auto_ptr(T* p = NULL) : ptr(p) {} /* prevents cast/conversion */ Girlfriend(const std::string& name) : name(name) {
~auto_ptr() { delete ptr; } std::cout << name << " is now your loyal partner! 👧\n";
T& operator*() { return *ptr; } }
T* operator->() { return ptr; } /* fakes being a pointer */ ~Girlfriend() {
private: std::cout << name << " has broken up with you. 😭\n";
T* ptr; }
std::string name;
}; };
```
- And lets start with some example code without smart pointers: void takeCareOfHer(std::unique_ptr<Girlfriend> g) {
std::cout << g->name << " is enjoying life with you!\n";
```cpp
void foo() {
Polygon* p(new Polygon(/* stuff */));
p->DoSomething();
delete p;
} }
```
- Heres how we can re-write the same example with our auto_ptr:
```cpp int main() {
void foo() { std::unique_ptr<Girlfriend> myGirl = std::make_unique<Girlfriend>("Taylor");
auto_ptr<Polygon> p(new Polygon(/* stuff */);
p->DoSomething(); // Uncommenting below would cause a compile error:
// std::unique_ptr<Girlfriend> copy = myGirl; // ❌ can't copy
takeCareOfHer(std::move(myGirl)); // Transfer ownership
if (!myGirl) {
std::cout << "You no longer have a girlfriend. 💔\n";
}
} }
``` ```
- We dont have to call delete! Theres no memory leak or memory error in this code. Awesome! ### 27.14.2 std::shared_ptr
## 27.13 So, What are the Advantages of Smart Pointers? - Shared ownership — multiple smart pointers can own the same object.
- With practice, smart pointers can result in code that is more concise and elegant with fewer errors. Why? ... - Uses reference counting to track owners.
- With thoughtful use, smart pointers make it easier to follow the principles of RAII and make code exception safe. In the auto_ptr example above, if DoSomething throws an exception, the memory for object p will be properly deallocated when we leave the scope of the foo function! This is not the case with the original version.
- The STL shared_ptr flavor implements reference counting garbage collection. Awesome<sup>2</sup>! - Object is deleted when the last owner is gone.
- They play nice with STL containers. Say you make an std::vector (or std::list, or std::map, etc.) of regular pointers to Polygon objects, Polygon* (especially handy if this is a polymorphic collection of objects!).
You allocate them all with new, and when you are all finished you must remember to explicitly deallocate each - Use Cases: When multiple parts of a program need access to the same object.
of the objects.
```cpp ```cpp
class Polygon { /*...*/ }; #include <memory>
class Triangle : public Polygon { /*...*/ };
class Quad : public Polygon { /*...*/ }; std::shared_ptr<int> p1 = std::make_shared<int>(20);
std::vector<Polygon*> polys; std::shared_ptr<int> p2 = p1; // shared ownership
polys.push_back(new Triangle(/*...*/)); ```
polys.push_back(new Quad(/*...*/));
for (unsigned int i = 0; i < polys.size(); i++) { - Reference Count: Use use_count() to check how many shared_ptrs refer to the object:
delete polys[i];
```cpp
p1.use_count(); // e.g., 2
```
Below is an std::shared_ptr example program:
```cpp
#include <iostream>
#include <memory>
#include <string>
class Pizza {
public:
Pizza(const std::string& type) : type(type) {
std::cout << type << " pizza is served! 🍕\n";
}
~Pizza() {
std::cout << type << " pizza is all gone! 😢\n";
}
std::string type;
};
void party() {
std::shared_ptr<Pizza> pizza = std::make_shared<Pizza>("Pepperoni");
std::shared_ptr<Pizza> alice = pizza;
std::shared_ptr<Pizza> bob = pizza;
std::cout << "Alice and Bob are enjoying the same " << pizza->type << " pizza! 🍴\n";
std::cout << "Pizza has " << pizza.use_count() << " fans right now.\n";
}
int main() {
party(); // All shared_ptrs go out of scope here
} }
polys.clear();
``` ```
In contrast, with smart pointers they will be deallocated automagically! ### 27.14.3 other smart pointers
```cpp - std::weak_ptr Use with shared_ptr. Memory is destroyed when no more shared_ptrs are pointing to object. So each time a weak_ptr is used you should first “lock” the data by creating a shared_ptr.
std::vector<shared_ptr<Polygon> > polys;
polys.push_back(shared_ptr<Polygon>(new Triangle(/*...*/)));
polys.push_back(shared_ptr<Polygon>(new Quad(/*...*/)));
polys.clear(); // cleanup is automatic!
```
## 27.14 Why are Smart Pointers Tricky?
- Smart pointers do not alleviate the need to master pointers, basic memory allocation & deallocation, copy constructors, destructors, assignment operators, and reference variables.
- You can still make mistakes in your smart pointer code that yield the same types of memory corruption,
segmentation faults, and memory leaks as regular pointers.
- There are several different flavors of smart pointers to choose from (developed for different uses, for common
design patterns). You need to understand your application and the different pitfalls when you select the
appropriate implementation.
## 27.15 What are the Different Types of Smart Pointers?
Like other parts of the C++ standard, these tools are still evolving. The different choices reflect different ownership
semantics and different design patterns. There are some smart pointers in STL, and also some in Boost (a C++
library that further extends the current STL). A quick overview:
- auto_ptr
When “copied” (copy constructor), the new object takes ownership and the old object is now empty. Deprecated
in new C++ standard.
- unique_ptr
Cannot be copied (copy constructor not public). Can only be “moved” to transfer ownership. Explicit ownership
transfer. Intended to replace auto_ptr. std::unique ptr has memory overhead only if you provide it with some
non-trivial deleter. It has time overhead only during constructor (if it has to copy the provided deleter) and
during destructor (to destroy the owned object).
- scoped_ptr (Boost)
“Remembers” to delete things when they go out of scope. Alternate to auto_ptr. Cannot be copied.
- shared_ptr
Reference counted ownership of pointer. Unfortunately, circular references are still a problem. Different subflavors based on where the counter is stored in memory relative to the object, e.g., intrusive_ptr, which
is more memory efficient. std::unique ptr has memory overhead only if you provide it with some non-trivial
deleter. It has time overhead in constructor (to create the reference counter), in destructor (to decrement
the reference counter and possibly destroy the object) and in assignment operator (to increment the reference
counter).
- weak_ptr
Use with shared_ptr. Memory is destroyed when no more shared_ptrs are pointing to object. So each time
a weak_ptr is used you should first “lock” the data by creating a shared_ptr.
- scoped_array and shared_array (Boost)
- std::scoped_ptr (Boost) “Remembers” to delete things when they go out of scope. Alternate to auto_ptr. Cannot be copied.