C2cpp Notes: Smart Pointers
Published:
This page is under construction.
17. Smart Pointer
Suicidal Pointer
A class with pointer inside and acts like a pointer (op*(),op->())
- Self-delete when goes out of scope
- How making two copies? Who’s deleting it?
- Shared pointer: know each other; deleted only after all gone
- Unique pointer: Only one can hold it; can only be moved
Shared Pointer
Implementation
template <typename T> class SharedPtr {
T* ptr;
int* count;
public:
explicit SharedPtr(T* p = nullptr): ptr{p}, count{new int{1}} {}
~SharedPtr() {
if(--*count == 0) {
delete count; delete ptr;
}
}
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
operator void*() const { return ptr; } // for if (sp)
T* get_prt() const { return ptr; } // get() in STL
int get_count() const { return *count; }
- Constructed with a pointer (E.
new string{}) - Manages heap-allocated data and
count - Last one out of scope is responsible for deletion
SharedPtr*(const SharedPtr<T>& sp): ptr(sp.ptr), count(sp.count) {
++*count;
}
SharedPtr<T>& operator=(const SharedPtr<T>& sp) {
if (this != &sp) { // delete, and then construct
if (--*count == 0) {delete count; delete sp;}
ptr = sp.ptr;
count = sp.count;
++*count;
}
return *this;
}
};
- Copy constructor makes a shallow copy of
ptrandcount; increments*count - Copy assignment, need detach first
Example
SharedPtr<string> p1 = {new string{"hello"}};
auto foo = [=] (SharedPtr<string> p) { // take p by value, CC!
cout << p.get_count() << endl;
}
foo(p1);
This will invoke copy constructor, incrementing count After foo() returns, p goes out of scope, and count decrements back
Vector
Let SharedPtr<string> contain a string* inside
auto create_vec1() {
SharedPtr<string> p1 { new string{"hello"} };
SharedPtr<string> p2 { new string{"c"} };
SharedPtr<string> p3 { new string{"students"} };
vector v { p1, p2, p3 };
return v;
}
vector v1 = create_vec1();
- Shared pointers themselves
p1… are stack-allocatedstringobjects are heap allocated
- When creating a vector, will make another copy of the string!
- Should be
vector<SharedPtr<s
- Should be
- Return by value:
vwill get moved/elided- Stack-allocated
p1goes out of scope,countgoes back to 1When
create_vec1()returns, stack pointers destroyed, andvmoved/elided Now modify it: ```cpp vector v2 = v1; // copy pointers, but still same underlying string *v2[1] = “c2cpp”; *v2[2] = “hackers”;
- Stack-allocated
// print v1 again for (auto&& p : v1) { cout « *p + “ “; } cout « ‘\n’;
1. **Copy construct** `v1` to `v2`
- `count` goes up to 2
2. Modify `v2`: Dereference `SharedPtr` as *raw* pointer and modify it!
3. Print original `v1`: **also modified**!

## Make Shared Pointer
Does the `new` with **any number** of arguments to construct `T`
- [Forwarding Reference](#forwarding-reference)!
```cpp
template <typename T, typename... Args>
SharedPtr<T> MakeSharedPtr(Args&&... args) {
return SharedPtr<T>{ new T{forward<Args>(args)...} };
}
auto p1 { MakeSharedPtr<string>("hello") }; // My implementation
auto p2 { make_shared<string>("c") }; // STL
static_assert(is_same_v< decltype(p2), shared_prt<string> >);
{
string* p0 { new string(50, 'A') }; // leak string and char[]
shared_ptr<string> p1 { new string(50, 'B') };
shared_ptr<string> p2 { new MakeSharedPtr<string>(50, 'C') };
}
MakeSharedPtr has one fewer allocation from Valgrind
- Library notices that
new stringis on the heap, so it combines the control blockcountandstringin a single allocation (placementnew) - Our implementation uses
new string, no such freedom
Deletion
{
SharedPtr<string> p0 { new string[2] {"a", "b"} };
}
Construction fine, but deletion crashes for string[2] ! Need delete[]
Need to write custom functor for delete
SharedPtr<string> p0 {new string[2] {"a", "b"},
[](string* s) {delete[] s;}
}
Can also do for FILEs
shared_ptr<FILE> sp { fp,
[](FILE* fp) {fclose(fp);}
}
Or, directly put <string[]> as the type for STL
for (SharedPtr<string> p : v1) { cout << *p + " "; }
for (auto p : v1)
for (auto& p : v1)
for (const auto& p : v1)
for (auto&& p : v1)
auto&&declares a forwarding referencexcan either be an L-value ref or R-value ref, depending on category of*v1.begin()- Useful if iterator return is unknown
- E.
vector<bool>
