std::unique_ptr
unique_ptr
It is a pointer to exclusive resource ownership. unique_ptr is allocated on the stack and then freed after leaving the scope, deleting the Resource object held inside.
As of C++ 11, we can use unique_ptr like this.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#include <iostream>
#include <memory> // for std::unique_ptr
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
};
int main()
{
// allocate a Resource object and have it owned by std::unique_ptr
std::unique_ptr<Resource> res{ new Resource() };
return 0;
} // res goes out of scope here, and the allocated Resource is destroyed
|
New in C++14 is the make_unique
function, which allows us to construct a unique_ptr
object (supporting array objects).
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
30
31
32
33
34
35
|
#include <memory> // for std::unique_ptr and std::make_unique
#include <iostream>
class Fraction
{
private:
int m_numerator{ 0 };
int m_denominator{ 1 };
public:
Fraction(int numerator = 0, int denominator = 1) :
m_numerator{ numerator }, m_denominator{ denominator }
{
}
friend std::ostream& operator<<(std::ostream& out, const Fraction &f1)
{
out << f1.m_numerator << '/' << f1.m_denominator;
return out;
}
};
int main()
{
// Create a single dynamically allocated Fraction with numerator 3 and denominator 5
// We can also use automatic type deduction to good effect here
auto f1{ std::make_unique<Fraction>(3, 5) };
std::cout << *f1 << '\n';
// Create a dynamically allocated array of Fractions of length 4
auto f2{ std::make_unique<Fraction[]>(4) };
std::cout << f2[0] << '\n';
return 0;
}
|
The output is as follows.
operation unique_ptr
unique_ptr removes the copy constructor and copy assignment operator, so it uses the move semantics when assigning values, and must use move to transfer objects when transferring them.
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 <iostream>
#include <memory> // for std::unique_ptr
#include <utility> // for std::move
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
};
int main()
{
std::unique_ptr<Resource> res1{ new Resource{} }; // Resource created here
std::unique_ptr<Resource> res2{}; // Start as nullptr
std::cout << "res1 is " << (res1 ? "not null\n" : "null\n");
std::cout << "res2 is " << (res2 ? "not null\n" : "null\n");
// res2 = res1; // Won't compile: copy assignment is disabled
res2 = std::move(res1); // res2 assumes ownership, res1 is set to null
std::cout << "Ownership transferred\n";
std::cout << "res1 is " << (res1 ? "not null\n" : "null\n");
std::cout << "res2 is " << (res2 ? "not null\n" : "null\n");
return 0;
} // Resource destroyed here when res2 goes out of scope
|
The output is as follows.
1
2
3
4
5
6
7
|
Resource acquired
res1 is not null
res2 is null
Ownership transferred
res1 is null
res2 is not null
Resource destroyed
|
unique_ptr also overloads operator*
and operator->
, where operator*
returns a reference to an object and operator->
returns a pointer to an object. Note that the objects managed by unique_ptr can be transferred, so you can determine this by if before using these two overloaded methods.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#include <iostream>
#include <memory> // for std::unique_ptr
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
friend std::ostream& operator<<(std::ostream& out, const Resource &res)
{
out << "I am a resource";
return out;
}
};
int main()
{
std::unique_ptr<Resource> res{ new Resource{} };
if (res) // use implicit cast to bool to ensure res contains a Resource
std::cout << *res << '\n'; // print the Resource that res is owning
return 0;
}
|
The output is as follows.
1
2
3
|
Resource acquired
I am a resource
Resource destroyed
|
Normally, we don’t have to worry about releasing objects managed by unique_ptr after we use it. But sometimes we still want to get back ownership of the object, so we can use the release function to return the pointer to the object managed by unique_ptr and release control of the pointer.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> uptr(new int(10)); //Binding dynamic objects
std::unique_ptr<int> uptr2 = std::move(uptr); //Transfer of Ownership
if(uptr == nullptr)
cout<<"uptr give up *int"<<endl;
int * p = uptr2.release(); //uptr2 releases control of the pointer, returns the pointer, and sets uptr2 to null
if(uptr2 == nullptr)
cout<<"uptr2 give up *int"<<endl;
cout<< *p <<endl;
delete p;
return 0;
}
|
unique_ptr as an argument and return value
unique_ptr passed as an argument will not be copied, but will transfer ownership of the object to the function, as follows ptr will be destroyed before the main method ends.
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
30
31
32
33
|
#include <iostream>
#include <memory> // for std::unique_ptr
#include <utility> // for std::move
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
friend std::ostream& operator<<(std::ostream& out, const Resource &res)
{
out << "I am a resource";
return out;
}
};
void takeOwnership(std::unique_ptr<Resource> res)
{
if (res)
std::cout << *res << '\n';
} // the Resource is destroyed here
int main()
{
auto ptr{ std::make_unique<Resource>() };
// takeOwnership(ptr); // This doesn't work, need to use move semantics
takeOwnership(std::move(ptr)); // ok: use move semantics
std::cout << "Ending program\n";
return 0;
}
|
The output is as follows.
1
2
3
4
|
Resource acquired
I am a resource
Resource destroyed
Ending program
|
Sometimes you don’t want to transfer the ownership of the object to the function, then you can get the object through the get method, as follows.
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
30
31
32
33
34
35
|
#include <memory> // for std::unique_ptr
#include <iostream>
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
friend std::ostream& operator<<(std::ostream& out, const Resource &res)
{
out << "I am a resource";
return out;
}
};
// The function only uses the resource, so we'll accept a pointer to the resource, not a reference to the whole std::unique_ptr<Resource>
void useResource(Resource* res)
{
if (res)
std::cout << *res << '\n';
else
std::cout << "No resource\n";
}
int main()
{
auto ptr{ std::make_unique<Resource>() };
useResource(ptr.get()); // note: get() used here to get a pointer to the Resource
std::cout << "Ending program\n";
return 0;
} // The Resource is destroyed here
|
The output is as follows.
1
2
3
4
|
Resource acquired
I am a resource
Ending program
Resource destroyed
|
unique_ptr can be returned directly as a return value.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#include <memory> // for std::unique_ptr
std::unique_ptr<Resource> createResource()
{
return std::make_unique<Resource>();
}
int main()
{
auto ptr{ createResource() };
// do whatever
return 0;
}
|
In C++14 and earlier versions would use the move semantics to return unique_ptr objects, in C++17 and later versions, the move operation is also not needed because the return value optimization is forced on, and RVO optimization is performed (see here for details: https://en.wikipedia.org/wiki/Copy_elision), so it is safer to return the value directly than to return a raw pointer or reference.
Caution
Do not let multiple unique_ptrs hold the same object. As follows, res1 and res2 will try to release res multiple times.
1
2
3
|
Resource* res{ new Resource() };
std::unique_ptr<Resource> res1{ res };
std::unique_ptr<Resource> res2{ res };
|
Do not delete an object manually after holding it with unique_ptr, as this will cause an object to be freed multiple times.
1
2
3
|
Resource* res{ new Resource() };
std::unique_ptr<Resource> res1{ res };
delete res;
|
Note also the exception issue, using unique_ptr does not absolutely guarantee exception safety, as follows.
1
|
some_function(std::unique_ptr<T>(new T), function_that_can_throw_exception());
|
The C++ standard does not specify the order in which the compiler evaluates function arguments, so it is possible to have an order such as
- calls to
new T
to allocate dynamic memory.
- calling the
function_that_can_throw_exception()
function.
- Calling the constructor of unique_ptr.
If function_that_can_throw_exception
is called and unique_ptr is not constructed at that time, the memory allocated by new T
cannot be reclaimed, resulting in a memory leak. To solve this problem, you need to use the make_unique
function.
1
|
some_function(std::make_unique<T>(), function_that_can_throw_exception());
|
Because the creation of object T and unique_ptr are handled in the make_unique function, the above order problem does not occur.
std::shared_ptr
shared_ptr is different from unique_ptr in that the resources it manages can be held by multiple objects. In the underlying implementation, shared_ptr is implemented using reference counting, which has an internal controller type hosting a counter, and this internal controller type object is stored in shared_ptr as a pointer when shared_ptr is first constructed.
shared_ptr overloads the assignment operator, and this pointer is shared by another shared_ptr when it is assigned and copied to construct another shared_ptr. When the reference count goes to zero, this internally typed pointer is released along with the resources managed by shared_ptr.
In addition, to ensure thread safety, the reference counter’s plus 1 and minus 1 operations are atomic operations, which ensure that no problems occur when the shared_ptr is shared by multiple threads.
For simple use see the following example.
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
|
#include <iostream>
#include <memory> // for std::shared_ptr
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
};
int main()
{
// allocate a Resource object and have it owned by std::shared_ptr
Resource* res { new Resource };
std::shared_ptr<Resource> ptr1{ res };
{
std::shared_ptr<Resource> ptr2 { ptr1 }; // make another std::shared_ptr pointing to the same thing
std::cout << "Killing one shared pointer\n";
} // ptr2 goes out of scope here, but nothing happens
std::cout << "Killing another shared pointer\n";
return 0;
} // ptr1 goes out of scope here, and the allocated Resource is destroyed
|
The output is as follows.
1
2
3
4
|
Resource acquired
Killing one shared pointer
Killing another shared pointer
Resource destroyed
|
In the above example, ptr2 is created in its own scope, and then out of scope ptr2 is destroyed, but the managed resources are not destroyed until after the main method is finished.
Here’s another example, which is one of the wrong uses for many people.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#include <iostream>
#include <memory> // for std::shared_ptr
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
};
int main()
{
Resource* res { new Resource };
std::shared_ptr<Resource> ptr1 { res };
{
std::shared_ptr<Resource> ptr2 { res }; // create ptr2 directly from res (instead of ptr1)
std::cout << "Killing one shared pointer\n";
} // ptr2 goes out of scope here, and the allocated Resource is destroyed
std::cout << "Killing another shared pointer\n";
return 0;
} // ptr1 goes out of scope here, and the allocated Resource is destroyed again
|
The output is as follows.
1
2
3
4
5
|
Resource acquired
Killing one shared pointer
Resource destroyed
Killing another shared pointer
Resource destroyed
|
The above example will cause a crash because the same resource is deleted twice. We notice that ptr2 is not copied from ptr1, but created directly. Even though the resources managed internally are the same, they are not related to each other, so when ptr2 goes out of scope, it will be freed, and ptr1 will be freed again after the main method ends.
So if you need a shared_ptr to point to the same resource, then best practice is to copy it out of an existing shared_ptr.
Also new in C++14 is the make_shared function, which we can use to construct a shared_ptr object.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#include <iostream>
#include <memory> // for std::shared_ptr
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
};
int main()
{
// allocate a Resource object and have it owned by std::shared_ptr
auto ptr1 { std::make_shared<Resource>() };
{
auto ptr2 { ptr1 }; // create ptr2 using copy of ptr1
std::cout << "Killing one shared pointer\n";
} // ptr2 goes out of scope here, but nothing happens
std::cout << "Killing another shared pointer\n";
return 0;
} // ptr1 goes out of scope here, and the allocated Resource is destroyed
|
In C++ 14 onwards, you should also try to use make_shared to create objects, as it ensures better performance in memory allocation and can also solve the problems caused by the initialization order of function arguments by the compiler (consistent with the unique_ptr problem above).
enable_shared_from_this
If you accidentally return this object directly inside a class and try to get the shared_ptr of that object, it will cause an object to be deleted twice, as follows.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class Resource
{
public:
Resource() { std::cerr << "Resource acquired\n"; }
~Resource() { std::cerr << "Resource destroyed\n"; }
std::shared_ptr<Resource> GetSPtr() {
return std::shared_ptr<Resource>(this);
}
};
int main()
{
auto sptr1 = std::make_shared<Resource>();
auto sptr2 = sptr1->GetSPtr();
return 0;
}
|
The output is as follows.
1
2
3
|
Resource acquired
Resource destroyed
double free or corruption (out)
|
The above code actually generates two separate shared_ptr’s with separate control blocks, so it causes the Resource to be released twice.
So we use enable_shared_from_this to avoid the above situation.
1
2
3
4
5
6
7
8
9
|
class Resource : public std::enable_shared_from_this<Resource>
{
public:
Resource() { std::cerr << "Resource acquired\n"; }
~Resource() { std::cerr << "Resource destroyed\n"; }
std::shared_ptr<Resource> GetSPtr() {
return shared_from_this();
}
};
|
Note
The first issue is resource release. When using shared_ptr you must be careful to dispose of it properly, because it is possible that a shared_ptr is not released and thus the resources it manages are not released. With unique_ptr you only need to care if one object is freed, but with shared_ptr you need to care if all objects are freed. Take for example the following circular reference problem.
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
#include <iostream>
#include <memory> // for std::shared_ptr
#include <string>
class Person
{
std::string m_name;
std::shared_ptr<Person> m_partner; // initially created empty
public:
Person(const std::string &name): m_name(name)
{
std::cout << m_name << " created\n";
}
~Person()
{
std::cout << m_name << " destroyed\n";
}
friend bool partnerUp(std::shared_ptr<Person> &p1, std::shared_ptr<Person> &p2)
{
if (!p1 || !p2)
return false;
p1->m_partner = p2;
p2->m_partner = p1;
std::cout << p1->m_name << " is now partnered with " << p2->m_name << '\n';
return true;
}
};
int main()
{
auto lucy { std::make_shared<Person>("Lucy") }; // create a Person named "Lucy"
auto ricky { std::make_shared<Person>("Ricky") }; // create a Person named "Ricky"
partnerUp(lucy, ricky); // Make "Lucy" point to "Ricky" and vice-versa
return 0;
}
|
In the above we created two Persons Lucy and Ricky, and then let their internal m_partner assign values to each other, resulting in a circular reference to both of them, neither of which can be released.
The output is as follows.
1
2
3
|
Lucy created
Ricky created
Lucy is now partnered with Ricky
|
Second, arrays are a problem. Before C++17 shared_ptr was not good at hosting arrays, so you should not use shared_ptr to host C-style arrays in C++17 and earlier. Before C++17 shared_ptr would delete the managed arrays by delete instead of delete[]
.
So we have two ways to solve this problem, the first is to use vector instead of new[]
, e.g. shared_ptr<vector<int>>
; the second is a custom deleter, e.g.
1
2
3
4
5
6
7
8
|
template< typename T >
struct array_deleter
{
void operator ()( T const * p)
{
delete[] p;
}
};
|
Creating a shared_ptr should be written like this.
1
|
std::shared_ptr<int> sp(new int[10], array_deleter<int>());
|
Details can be found here: https://stackoverflow.com/questions/13061979/shared-ptr-to-an-array-should-it-be-used
The third is that we can convert from unique_ptr to shared_ptr, but shared_ptr is not convertible to unique_ptr. For example.
1
2
3
4
5
|
std::unique_ptr<std::string> unique = std::make_unique<std::string>("test");
std::shared_ptr<std::string> shared = std::move(unique);
//or
std::shared_ptr<std::string> shared = std::make_unique<std::string>("test");
|
std::weak_ptr
Above we introduced the problem that shared_ptr
may not free objects due to circular references, and weak_ptr
is designed to solve this problem. So weak_ptr is a smart pointer introduced to work with shared_ptr
, it doesn’t actually manage the object, it points to an object managed by shared_ptr
without affecting the life cycle of the object it refers to, i.e. binding a weak_ptr
to a shared_ptr
doesn’t change the reference count of the shared _ptr
’s reference count.
So let’s see how to solve the circular reference problem with weak_ptr in the above example.
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
#include <iostream>
#include <memory> // for std::shared_ptr and std::weak_ptr
#include <string>
class Person
{
std::string m_name;
std::weak_ptr<Person> m_partner; // note: This is now a std::weak_ptr
public:
Person(const std::string &name): m_name(name)
{
std::cout << m_name << " created\n";
}
~Person()
{
std::cout << m_name << " destroyed\n";
}
friend bool partnerUp(std::shared_ptr<Person> &p1, std::shared_ptr<Person> &p2)
{
if (!p1 || !p2)
return false;
p1->m_partner = p2;
p2->m_partner = p1;
std::cout << p1->m_name << " is now partnered with " << p2->m_name << '\n';
return true;
}
};
int main()
{
auto lucy { std::make_shared<Person>("Lucy") };
auto ricky { std::make_shared<Person>("Ricky") };
partnerUp(lucy, ricky);
return 0;
}
|
The output is as follows.
1
2
3
4
5
|
Lucy created
Ricky created
Lucy is now partnered with Ricky
Ricky destroyed
Lucy destroyed
|
Because Person uses weak_ptr internally, so in the above example, we point lucy’s m_partner to Ricky, but because it is a weak_ptr, it does not count, so it can be destroyed normally after the main method is finished.
Because weak_ptr does not implement the ->
operator, so in general you cannot use it directly, so we can convert weak_ptr to shared_ptr by using the lock method of weak_ptr, as follows.
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
#include <iostream>
#include <memory> // for std::shared_ptr and std::weak_ptr
#include <string>
class Person
{
std::string m_name;
std::weak_ptr<Person> m_partner; // note: This is now a std::weak_ptr
public:
Person(const std::string &name) : m_name(name)
{
std::cout << m_name << " created\n";
}
~Person()
{
std::cout << m_name << " destroyed\n";
}
friend bool partnerUp(std::shared_ptr<Person> &p1, std::shared_ptr<Person> &p2)
{
if (!p1 || !p2)
return false;
p1->m_partner = p2;
p2->m_partner = p1;
std::cout << p1->m_name << " is now partnered with " << p2->m_name << '\n';
return true;
}
const std::shared_ptr<Person> getPartner() const { return m_partner.lock(); } // use lock() to convert weak_ptr to shared_ptr
const std::string& getName() const { return m_name; }
};
int main()
{
auto lucy { std::make_shared<Person>("Lucy") };
auto ricky { std::make_shared<Person>("Ricky") };
partnerUp(lucy, ricky);
auto partner = ricky->getPartner(); // get shared_ptr to Ricky's partner
std::cout << ricky->getName() << "'s partner is: " << partner->getName() << '\n';
return 0;
}
|
The output is as follows.
1
2
3
4
5
6
|
Lucy created
Ricky created
Lucy is now partnered with Ricky
Ricky's partner is: Lucy
Ricky destroyed
Lucy destroyed
|
weak_ptr can also avoid dangling pointers
Dangling pointers are objects that are freed, but the target of the pointer is not modified, resulting in a pointer to a freed piece of memory, which may result in duplicate frees and other unpredictable behavior.
For a raw pointer, we cannot know whether the address pointed to by the pointer is a dangling pointers or not, so for weak_ptr we can use the expired method to check whether the object pointed to by weak_ptr has been released or not, thus solving this problem to some extent.
Let’s look at an example.
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
30
31
32
33
34
35
|
// h/t to reader Waldo for an early version of this example
#include <iostream>
#include <memory>
class Resource
{
public:
Resource() { std::cerr << "Resource acquired\n"; }
~Resource() { std::cerr << "Resource destroyed\n"; }
};
// Returns a std::weak_ptr to an invalid object
std::weak_ptr<Resource> getWeakPtr()
{
auto ptr{ std::make_shared<Resource>() };
return std::weak_ptr{ ptr };
} // ptr goes out of scope, Resource destroyed
// Returns a dumb pointer to an invalid object
Resource* getDumbPtr()
{
auto ptr{ std::make_unique<Resource>() };
return ptr.get();
} // ptr goes out of scope, Resource destroyed
int main()
{
auto dumb{ getDumbPtr() };//Returned raw pointer
std::cout << "Our dumb ptr is: " << ((dumb == nullptr) ? "nullptr\n" : "non-null\n");
auto weak{ getWeakPtr() };//Return weak_ptr
std::cout << "Our weak ptr is: " << ((weak.expired()) ? "expired\n" : "valid\n");
return 0;
}
|
The output is as follows.
1
2
3
4
5
6
|
Resource acquired
Resource destroyed
Our dumb ptr is: non-null
Resource acquired
Resource destroyed
Our weak ptr is: expired
|
In the above example the getDumbPtr method uses a unique_ptr to host the object and returns a pointer to the object hosted inside, so the object will be destroyed at the end of the getDumbPtr method, so getDumbPtr returns a dangling pointers and finally we find that dumb still points to an address, but the object at that address has been freed.
For the getWeakPtr method, weak_ptr is returned, but as we said above, weak_ptr does not host objects, so at the end of the getWeakPtr method, the object returned by getWeakPtr is actually destroyed by share_ptr. So in the main function weak actually points to an invalid memory region, and we can know if the object it points to has been destroyed by calling the weak.expired()
method.
weak_ptr implementation cache
Because it is a separate expired operation, there may be concurrency issues, such as the object being destroyed after expired, and then using it again may produce unpredictable results. So for this kind of judgment, we can leave it to the lock function. If the weak_ptr is not destroyed, then it will return shared_ptr, which is equivalent to extending its lifetime because it increments a reference count, and if the weak_ptr has been destroyed, then it will return nil.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// Returns a std::weak_ptr to an invalid object
std::weak_ptr<Resource> getWeakPtr()
{
auto ptr{ std::make_shared<Resource>() };
return std::weak_ptr{ ptr };
} // ptr goes out of scope, Resource destroyed
int main()
{
std::shared_ptr<Resource> spw = getWeakPtr().lock(); //return null
std::cout << "Our share ptr is: " << ((spw == nullptr) ? "nullptr\n" : "non-null\n");
std::shared_ptr<Resource> spw2(getWeakPtr()); //if object is expired,throw std::bad_weak_ptr
return 0;
}
|
The output is as follows.
1
2
3
4
5
6
7
|
Resource acquired
Resource destroyed
Our share ptr is: nullptr
Resource acquired
Resource destroyed
terminate called after throwing an instance of 'std::bad_weak_ptr'
what(): bad_weak_ptr
|
In the above example, spw will return null and spw2 will throw an exception. So with lock we can implement cache operations. For example, if we have a global map as a cache, and the value inside is weak_ptr, then each time we get the cache, we can determine whether the value returned by lock is null, and return it directly if it is not null, and reload the cache if it is null.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
std::shared_ptr<const Widget> fastLoadWidget(WidgetID id)
{
static std::unordered_map<WidgetID,std::weak_ptr<const Widget>> cache;
// objPtr is std::shared_ptr to cached object (or null
// if object's not in cache)
auto objPtr = cache[id].lock();
if (!objPtr) { // if not in cache,
objPtr = loadWidget(id); // load it
cache[id] = objPtr; // cache it
}
return objPtr;
}
|
Summary
unique_ptr It is a pointer that takes exclusive ownership of a resource. unique_ptr will be allocated on the stack and then freed after leaving the scope, deleting the Resource object held inside, and it can only transfer objects using move semantics. So if you want to manipulate a pointer, allocate memory on entry to the scope and then safely release the object when you leave the scope, then you can use it.
shared_ptr It manages resources that can be held by multiple objects and uses a reference counting strategy to release objects; if the count is not cleared, then the resources it manages will not be released.
weak_ptr It does not manage objects, but is only an observer of the resources managed by the shared_ptr object, so it does not affect the lifecycle of shared resources, it is generally used in caching, resolving shared_ptr circular references, etc.
Ref
https://www.luozhiyun.com/archives/762