CS计算机代考程序代写 c++ COMP6771 Advanced C++ Programming

COMP6771 Advanced C++ Programming
Week 5.2 Smart Pointers
1

Recap: RAII – Making unnamed objects safe
Don’t use the new / delete keyword in your own code We are showing for demonstration purposes
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
// myintpointer.h
class MyIntPointer {
public:
// This is the constructor
MyIntPointer(int* value);
// This is the destructor
~MyIntPointer();
int*GetValue();
private:
int* value_;
};
10 11 12 13
MyIntPointer::~MyIntPointer() {
// Similar to C’s free function.
delete value_;
}
1 // myintpointer.cpp
2 #include “myintpointer.h”
3
4 MyIntPointer::MyIntPointer(int* value): value_{value} {} 5
6 int* MyIntPointer::GetValue() {
7 8} 9
return value_
1 void fn() {
2
3
4
5
6
7 8}
// Similar to C’s malloc
MyIntPointer p{new int{5}};
// Copy the pointer;
MyIntPointer q{p.GetValue()};
// p and q are both now destructed.
// What happens?
demo551-safepointer.cpp
2

Smart Pointers
Ways of wrapping unnamed (i.e. raw pointer) heap objects in named stack objects so that object lifetimes can be managed much easier
Introduced in C++11
Usually two ways of approaching problems:
unique_ptr + raw pointers (“observers”) shared_ptr + weak_ptr/raw pointers
Type
Shared ownership
Take ownership
std::unique_ptr
No
Yes
raw pointers
No
No
std::shared_ptr
Yes
Yes
std::weak_ptr
No
No
3

Unique pointer
std::unique_pointer
The unique pointer owns the object
When the unique pointer is destructed, the underlying object is too
raw pointer (observer)
Unique Ptr may have many observers
This is an appropriate use of raw pointers (or references) in C++ Once the original pointer is destructed, you must ensure you don’t access the raw pointers (no checks exist)
These observers do not have ownership of the pointer
Also note the use of ‘nullptr’ in C++ instead of NULL
4.1

Unique pointer: Usage
1 #include
2 #include
3
4 int main() {
5 std::unique_ptr up1{new int};
6 std::unique_ptr up2 = up1; // no copy constructor
7 std::unique_ptr up3;
8 up3 = up2; // no copy assignment
9
10 up3.reset(up1.release()); // OK
11 std::unique_ptr up4 = std::move(up3); // OK
12 std::cout << up4.get() << "\n"; 13 std::cout << *up4 << "\n"; 14 std::cout << *up1 << "\n"; 15 } demo552-unique1.cpp 4.2 Observer Ptr: Usage 1 #include
2 #include
3
4 int main() {
5 std::unique_ptr up1(new int{0});
6 *up1 = 5;
7 std::cout << *up1 << "\n"; 8 int* op1 = up1.get(); 9 *op1 = 6; 10 std::cout << *op1 << "\n"; 11 up1.reset(); 12 std::cout << *op1 << "\n"; 13 } demo553-observer.cpp Can we remove "new" completely? 4.3 Unique Ptr Operators This method avoids the need for "new". It has other benefits that we will explore. 1 #include
2 #include
3
4 int main() {
5 // 1 – Worst – you can accidentally own the resource multiple
6 // times, or easily forget to own it.
7 int *i = new int;
8 auto up1 = std::make_unique(i);
9 auto up11 = std::make_unique(i);
10
11 // 2 – Not good – requires actual thinking about whether there’s a leak.
12 std::unique_ptr up2{new std::string{“Hello”}};
13
14 // 3 – Good – no thinking required.
15 std::unique_ptr up3 = std::make_unique(“Hello”);
16
17 std::cout << *up3 << "\n"; 18 std::cout << *(up3.get()) << "\n"; 19 std::cout << up3->size();
20 }
demo554-unique2.cpp
https://stackoverflow.com/questions/37514509/advantages-of-using-stdmake-unique-over-new-operator https://stackoverflow.com/questions/20895648/difference-in-make-shared-and-normal-shared-ptr-in-c
4.4

Shared pointer
std::shared_pointer
Several shared pointers share ownership of the object
A reference counted pointer
When a shared pointer is destructed, if it is the only shared pointer left pointing at the object, then the object is destroyed
May also have many observers
Just because the pointer has shared ownership doesn’t mean the observers should get ownership too – don’t mindlessly copy it
std::weak_ptr
Weak pointers are used with share pointers when:
You don’t want to add to the reference count
You want to be able to check if the underlying data is still valid before using it.
5.1

Shared pointer: Usage
1 #include
2 #include
3
4 int main() {
5 std::shared_ptr x(new int{5});
6 std::shared_ptr y = x; // Both now own the memory
7 std::cout << "use count: " << x.use_count() << "\n"; 8 std::cout << "value: " << *x << "\n"; 9 x.reset(); // Memory still exists, due to y. 10 std::cout << "use count: " << y.use_count() << "\n"; 11 std::cout << "value: " << *y << "\n"; 12 y.reset(); // Deletes the memory, since 13 // no one else owns the memory 14 std::cout << "use count: " << x.use_count() << "\n"; 15 std::cout << "value: " << *y << "\n"; 16 } demo555-shared.cpp Can we remove "new" completely? 5.2 Weak Pointer: Usage 9 10 11 12 13 14 15 16 17 18 19 20 std::shared_ptr y = wp.lock(); // x and y own the memory
if (y) {
// Do something with y
std::cout << "Attempt 1: " << *y << '\n'; } } // y is destroyed. Memory is owned by x x.reset(); // Memory is deleted std::shared_ptr z = wp.lock(); // Memory gone; get null ptr
if (z) {
// will not execute this
std::cout << "Attempt 2: " << *z << '\n'; } 1 2 3 4 5 6 7{ 8 #include
#include
int main() {
std::shared_ptr x = std::make_shared(1);
std::weak_ptr wp = x; // x owns the memory
}
demo556-weak.cpp
5.3

When to use which type
Unique pointer vs shared pointer
You almost always want a unique pointer over a shared pointer
Use a shared pointer if either:
An object has multiple owners, and you don’t know which one will stay around the longest
You need temporary ownership (outside scope of this course) This is very rare
6.1

Smart pointer examples
Linked list
Doubly linked list
Tree
DAG (mutable and non-mutable)
Graph (mutable and non-mutable)
Twitter feed with multiple sections (eg. my posts, popular posts)
6.2

6.3

Stack unwinding
Stack unwinding is the process of exiting the stack frames until we find an exception handler for the function
This calls any destructors on the way out
Any resources not managed by destructors won’t get freed up
If an exception is thrown during stack unwinding, std::terminate is called Not safe Not safe Safe
1 void g() {
2 throw std::runtime_error{“”};
3}
4
5 int main() {
6 auto ptr = std::make_unique(5); 7 g();
8}
1 void g() {
2 throw std::runtime_error{“”}; 3}
4
5 int main() {
6 auto ptr = new int{5};
7 g();
8 // Never executed.
9 delete ptr;
10 }
1 void g() {
2 throw std::runtime_error{“”}; 3}
4
5
6
7
8
9}
int main() {
auto ptr = new int{5};
g();
auto uni = std::unique_ptr(ptr);
7.1

Exceptions & Destructors
During stack unwinding, std::terminate() will be called if an exception leaves a destructor
The resources may not be released properly if an exception leaves a destructor
All exceptions that occur inside a destructor should be handled inside the destructor
Destructors usually don’t throw, and need to explicitly opt in to throwing
STL types don’t do that
7.2

Partial construction
Spot the bug
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class my_int {
public:
my_int(int const i) : i_{i} {
if (i == 2) {
throw std::exception();
private:
int i_;
};
class unsafe_class {
public:
unsafe_class(int a, int b)
: a_{new my_int{a}}
, b_{new my_int{b}}
{}
~unsafe_class() {
delete a_;
delete b_;
} private:
my_int* a_;
my_int* b_;
};
int main() {
auto a = unsafe_class(1, 2);
}
1
2
3
4
5
6
7 8} 9}
#include
What happens if an exception is thrown halfway through a constructor?
The C++ standard: “An object that is partially constructed or partially destroyed will have destructors executed for all of its fully constructed subobjects”
A destructor is not called for an object that was partially constructed
Except for an exception thrown in a constructor that delegates (why?)
demo557-bad.cpp
7.3

Partial construction: Solution
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include
#include
class my_int {
public:
my_int(int const i)
: i_{i} {
if (i == 2) {
throw std::exception();
} }
private:
int i_;
};
class safe_class {
public:
safe_class(int a, int b)
: a_(std::make_unique(a))
, b_(std::make_unique(b))
{}
private:
std::unique_ptr a_;
std::unique_ptr b_;
};
int main() {
auto a = safe_class(1, 2);
}
Option 1: Try / catch in the constructor
Very messy, but works (if you get it right…)
Doesn’t work with initialiser lists (needs to be in the body)
Option 2:
An object managing a resource should initialise the resource last
The resource is only initialised when the whole object is
Consequence: An object can only manage one resource
If you want to manage multiple resources, instead manage several wrappers , which each manage one resource
demo558-partial1.cpp
7.4

Feedback
8