Note 12 24 SmartPointer
Note 12 24 SmartPointer
12/24/2014
delete p1;
delete p2;
delete p3;
The new operator computes the size of the requested memory. In this case, it takes an integer,
and it returns enough memory to hold an integer value.
int * p1 = new int;
*p1 = 1001;
delete p1;
p1 is set to point to that memory and now p1 and the associated code that uses it become the
owner of that memoryin other words, the code that uses p1 must eventually return this
memory back to the free store, an operation called freeing the memory. Until p1 is freed, the
memory that is pointed to will be marked as in-use and will not be given out again. If you
keep allocating memory and never free it, you will run out of memory. The delete operation
frees up the memory allocated through new.
Before C++ introduces STL, dynamic memory via the mechanism of new and delete is the
only way to create an array dynamically (e.g., std::vector<T>). You can dynamically
allocate an array of memory using new and assign that memory to a pointer:
1
Using the array syntax as the argument to new tells the compiler how much memory it needs
enough for an 8 element integer array. Now you can use p_numbers just as if it pointed to
an array. Unlike built-in arrays, though, you need to free the memory pointed to by
p_numbers. To free the memory, there is a special syntax for the delete operator:
delete[] p_numbers;
The brackets tell the compiler that the pointer points to an array of values, rather than a single
value.
In-class Exercise 12.1: write a program to dynamically allocate the requested memory for a
string array via new, change all the values to C++ and output the contents. Below is a
sample run:
A:
#include <iostream>
#include <string>
using namespace std;
int main()
{
cout << "Enter the size of the string array: ";
int num;
cin >> num;
string* parr = new string[num];
for (unsigned i = 0; i < num; ++i)
parr[i] = "C++";
cout << endl;
cout << "The new contents of the string array are: " << endl;
for (unsigned i = 0; i < num; ++i)
cout << parr[i] << " ";
cout << endl;
delete [] parr;
return 0;
}
Dynamic memory is problematic because it is surprisingly hard and error-prone. There are
2
By setting the pointer to nullptr, if your code does try to dereference the pointer after it is
freed, you will find out immediately because the program will crash right away.
3. Deleting the same memory twice. This error can happen when two pointers point to the
same dynamically allocated object. If delete is applied to one of the pointers, then the
objects memory is returned to the free store. If we subsequently delete the second
pointer, then the free store may be corrupted.
These kinds of errors are considerably easier to make than they are to find and fix (VERY
BAD).
A usual approach to avoid these kinds of problems is to use smart pointers. A smart
pointer is a template class object that acts like a pointer but has additional features. They are
smart in the sense that they support programmers in avoiding problems such as those just
described. For example, a smart pointer can be so smart that it knows whether it is the last
pointer to an object and uses this knowledge to delete an associated object only when it, as
last owner of an object, gets destroyed.
It is not sufficient to provide only one smart pointer class. Smart pointers can be smart about
different aspects. Since C++11, the C++ standard library provides two major types of smart
pointer:
1. Class shared_ptr for a pointer that implements the concept of shared ownership.
Multiple smart pointers can refer to the same object so that the object and its associated
resources get released whenever the last reference to it gets destroyed.
2. Class unique_ptr for a pointer that implements the concept of exclusive ownership or
3
We use a smart pointer in ways that are similar to using a pointer (a smart pointer is a
template class object that acts like a pointer but has additional features). Dereferencing a
smart pointer returns the object to which the pointer points. When we use a smart pointer in a
condition, the effect is to test whether the pointer is null:
// if p1 is not null, check whether it's the empty string
shared_ptr<string> p1; // shared_ptr that can point at a string
if (p1 && p1->empty())
*p1 = "hi"; // if so, dereference p1 to assign a new value to that string
Note that because the constructor from the class shared_ptr taking a pointer as single
argument is explicit, you CANNOT use the assignment notation here because that is
considered to be an implicit conversion.
shared_ptr<int> p2 = new int(42); // ERROR
Q: Make a shared_ptr that points to a list of ints initialized with 20 elements equal to
9.
A:
auto p = make_shared<list<int>>(20, 9);
This is known as a reference count or user count. Whenever we copy a shared_ptr, the
count is incremented. Once a shared_ptrs counter goes to zero, the shared_ptr
automatically (thus the smart pointer) frees the object that it manages:
auto p = make_shared<int>(42); // object to which p points has one user
auto q(p); // p and q point to the same object
5
Q: what are the user count for shared_ptr<int> p? what happens to the
shared_ptr<int> r?
A:
3 user count
r is the only shared_ptr pointing to the one we previously
allocated. That int is automatically freed as part of assigning q
to r.
We can trace the user count through shared_ptr member function use_count(). Notice
that this member function is primary for debugging/learning only and may be a slow
operation.
(Example)
#include <iostream>
#include <memory>
using namespace std;
int main()
{
auto p = make_shared<int>(42); // object to which p points
has one user
cout << "p" << endl;
cout << "Use count: " << p.use_count() << endl;
auto q(p); // p and q point to the same object
cout << "p q" << endl;
cout << "Use count: " << p.use_count() << endl;
cout << "Use count: " << q.use_count() << endl;
auto r = make_shared<int>(); // int to which r points has one
user
cout << "r" << endl;
cout << "Use count: " << r.use_count() << endl;
r = q;
cout << "p q r" << endl;
cout << "Use count: " << p.use_count() << endl;
cout << "Use count: " << q.use_count() << endl;
cout << "Use count: " << r.use_count() << endl;
return 0;
}
The fact that the shared_ptr class automatically frees dynamic objects when they are no
longer needed makes it fairly easy to use dynamic memory. For example, we might have a
function that returns a shared_ptr to a dynamically allocated object of a type named Foo
that can be initialized by an argument of type T:
// factory returns a shared_ptr pointing to a dynamically allocated
object
In contrast, we will have a hard time to manage dynamic objects with new:
// factory returns a shared_ptr pointing to a dynamically allocated
object
Remark: At the end of the program, when the last owner of a string gets destroyed, the
shared pointer automatically calls delete for the object it refers to.