Parent directory
Makefile
shared_ptr.cpp
smartptr.cpp
smartptr.h
unique_ptr.cpp
CC = g++
CXX = g++
CFLAGS = -g -Wall
CXXFLAGS = -g -Wall -std=c++17
# turn off warnings about unused variables
CXXFLAGS += -Wno-unused-variable
executables = smartptr shared_ptr unique_ptr
.PHONY: default
default: $(executables)
smartptr: smartptr.o
shared_ptr: shared_ptr.o
unique_ptr: unique_ptr.o
smartptr.o: smartptr.h
shared_ptr.o: smartptr.h
.PHONY: clean
clean:
rm -f *~ a.out core *.o $(executables)
.PHONY: all
all: clean default
#include <string>
#include <vector>
#include <iostream>
#include <iomanip>
#include <utility>
#include <memory>
#include <cassert>
#include "smartptr.h"
using namespace std;
int main() {
string* p0 { new string(50, 'A') };
shared_ptr<string> p1 { new string(50, 'B') };
shared_ptr<string> p2 { make_shared<string>(50, 'C') };
cout << *p0 << '\n' << *p1 << '\n' << *p2 << '\n';
}
/* Lecture outline:
1. make_shared<T>() combines allocation of T and the control block of
the shared_ptr<T>
- Compare the number of heap allocations using valgrind
- Draw diagram to illustrate what make_shared<T>() does
2. shared_ptr to an array and custom deleter
- Can SmartPtr point to an array?
SmartPtr<string> p { new string[2] {"hello", "c2cpp"} };
cout << *p << ' ' << p.getPtr()[1] << '\n';
- What about shared_ptr?
shared_ptr<string> p { new string[2] {"hello", "c2cpp"} };
cout << *p << ' ' << p.get()[1] << '\n';
- We can pass a custom deleter to shared_ptr:
shared_ptr<string> p { new string[2] {"hello", "c2cpp"},
[] (string* s) { delete[] s; }
};
cout << *p << ' ' << p.get()[1] << '\n';
- Or we can specify T as an array type:
shared_ptr<string[]> p { new string[2] {"hello", "c2cpp"} };
cout << p[0] << " " << p[1] << '\n';
Note that operator[] is now available for shared_ptr
3. Aliasing
shared_ptr<pair<string,int>> p1 { new pair{"hello"s, 4157} };
shared_ptr<int> p2 { p1, &p1->second };
cout << p1->first << ' ' << *p2 << '\n';
cout << p1.use_count() << '\n';
cout << p2.use_count() << '\n';
4. weak_ptr can be attached to shared_ptr without increasing ref count
auto weak_test = [](weak_ptr<string> wp) {
if (shared_ptr<string> sp = wp.lock()) {
cout << *sp << '\n';
} else {
cout << "weak_ptr expired\n";
}
};
weak_ptr<string> wp;
{
auto sp = make_shared<string>("hello");
assert(sp.use_count()==1);
wp = sp; // weak_ptr does not increase
assert(sp.use_count()==1); // the shared_ptr's ref count
weak_test(wp); // sp is alive, so is wp
}
weak_test(wp); // sp is gone, so wp is expired
*/
#include <string>
#include <vector>
#include <iostream>
#include <iomanip>
#include <utility>
#include <memory>
#include "smartptr.h"
using namespace std;
auto create_vec() {
// SmartPtr<T> replaces T* for heap-allocated object.
// That is, instead of doing
//
// string *p = new string("hello");
//
// We do the following:
SmartPtr<string> p1 { new string("hello") };
SmartPtr<string> p2 { new string("c") };
SmartPtr<string> p3 { new string("students") };
vector v { p1, p2, p3 };
return v;
}
int main() {
vector v1 = create_vec();
for (auto&& p : v1) { cout << *p + " "; } cout << '\n';
vector v2 = v1;
*v2[1] = "c2cpp";
*v2[2] = "hackers";
// print v1 again
for (auto&& p : v1) { cout << *p + " "; } cout << '\n';
}
/* Lecture outline:
1. Using SmartPtr<T> instead of T*
- create_vec() and main() code walk-thru
- SmartPtr<T> acts like T*, and no delete needed at the end
2. (Digression) A couple of new things about auto
- Letting compiler deduce return type using auto
- Using auto&& in range-for loop
- auto&& is a forwarding reference just like function template's T&&
- Four different ways to use auto in a range-for loop:
1) for (auto x : v) -- x is a copy of each element of v
2) for (auto& x : v) -- x is an l-value-ref to each element
3) for (auto&& x : v) -- x is either an l-value-ref or r-value-ref,
depending on what '*v.begin()' is.
4) for (const auto& x : v) -- x is a const l-value-ref
Note on 3): vector<bool> is an example where you get a temporary
object (i.e., an rvalue) when dereferencing an iterator.
3. SmartPtr implementation
- Drawing of SmartPtr basic-4 operations
- SmartPtr code walk-thru
- Drawing of create_vec() and main()
4. MakeSmartPtr
template <typename T, typename... Args>
SmartPtr<T> MakeSmartPtr(Args&&... args) {
return SmartPtr<T>(new T(std::forward<Args>(args)...));
}
auto create_vec() {
auto p1 { MakeSmartPtr<string>("hello") };
auto p2 { MakeSmartPtr<string>("c") };
auto p3 { MakeSmartPtr<string>("students") };
vector v { p1, p2, p3 };
return v;
}
5. Real smart pointer in STL: shared_ptr<T> and make_shared<T>
auto create_vec() {
// make_shared<T>() function template creates shared_ptr<T>
auto p1 { make_shared<string>("hello") };
auto p2 { make_shared<string>("c") };
auto p3 { make_shared<string>("students") };
vector v { p1, p2, p3 };
return v;
}
6. (Digression) static_assert, decltype, is_same_v
static_assert(is_same_v<decltype(p3), shared_ptr<string>>);
*/
template <class T>
class SmartPtr {
T* ptr; // the underlying pointer
int* count; // the reference count
public:
explicit SmartPtr(T *p = 0) : ptr{p}, count{new int(1)} {}
~SmartPtr() {
if (--*count == 0) {
delete count;
delete ptr;
}
}
SmartPtr(const SmartPtr<T>& sp) : ptr(sp.ptr), count(sp.count) {
++*count;
}
SmartPtr<T>& operator=(const SmartPtr<T>& sp) {
if (this != &sp) {
// first, detach.
if (--*count == 0) {
delete count;
delete ptr;
}
// attach to the new object.
ptr = sp.ptr;
count = sp.count;
++*count;
}
return *this;
}
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
T* getPtr() const { return ptr; } // access to underlying ptr
operator void*() const { return ptr; } // enables "if (sp) ..."
};
#include <string>
#include <vector>
#include <iostream>
#include <iomanip>
#include <utility>
#include <memory>
#include <cassert>
using namespace std;
unique_ptr<string[]> create_array(size_t n, string s) {
auto up = make_unique<string[]>(n);
for (size_t i = 0; i < n; ++i) {
up[i] = s;
}
return up;
}
int main() {
size_t n = 1000;
unique_ptr<string[]> up1 = create_array(n, "hello");
for (size_t i = 0; i < n; ++i) { assert(up1[i] == "hello"s); }
unique_ptr<string[]> up2;
assert(!up2);
// This doesn't compile -- unique_ptr cannot be copied
// up2 = up1;
// But it can be moved
up2 = std::move(up1);
assert(!up1 && up2);
// It can also be moved to a shared_ptr
shared_ptr<string[]> sp = std::move(up2);
assert(!up2 && sp.use_count() == 1);
for (size_t i = 0; i < n; ++i) { assert(sp[i] == "hello"s); }
}
/* Lecture outline:
1. std::unique_ptr is a simpler smart pointer
- It deletes the underlying object when it gets destroyed, just like
shared_ptr, but it doesn't do any reference counting.
- Thus, unique_ptr needs to ensure that there is only one unique_ptr
pointing to the underlying object at any time. It achieves this by
disallowing copy -- i.e., it cannot be copied; it can only be moved.
- This sample program demonstrates how it works.
- In most situations where you need a smart poninter, you don't really
need more than one pointer at a time. Start with a unique_ptr and
switch to shared_ptr only when needed.
*/