Please indicate the source: http://blog.csdn.net/gaoxiangnumber1
Welcome to my github: https://github.com/gaoxiangnumber1
- Objects allocated in static or stack memory are automatically created and destroyed by the compiler:
- Static memory is used for local static objects(§6.1.1), for class static data members(§7.6), and for variables defined outside any function; static objects are allocated before they are used, and they are destroyed when the program ends.
- Stack memory is used for nonstatic objects defined inside functions; stack objects exist only while the block in which they are defined is executing.
- Programs use the heap for objects that they dynamically allocate(i.e., allocates at run time). Dynamically allocated objects have a lifetime that is independent of where they are created; they exist until they are explicitly freed.
- To make using dynamic objects safer, the library defines two smart pointer types that manage dynamically allocated objects. Smart pointers ensure that the objects to which they point are automatically freed when it is appropriate to do so.
12.1. Dynamic Memory and Smart Pointers
- Dynamic memory is managed through a pair of operators: new(allocates and optionally initializes an object in dynamic memory and returns a pointer to that object) and delete(takes a pointer to a dynamic object, destroys that object, and frees the associated memory).
- It is hard to free memory at the right time. We may forget to free the memory(and we have a memory leak) or free the memory when there are still pointers referring to that memory(and we have a pointer that refers to memory that is no longer valid).
- C++11: shared_ptr allows multiple pointers to refer to the same object. unique_ptr owns the object to which it points. weak_ptr is a weak reference to an object managed by a shared_ptr. All three are defined in .
12.1.1. The shared_ptr Class
- Smart pointers are templates and when we create a smart pointer, we must supply the type to which the pointer can point. A default initialized smart pointer holds a null pointer.
- 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.
- Table 12.1 lists operations common to shared_ptr and unique_ptr. Those that are particular to shared_ptr are listed in Table 12.2(p. 453).
The make_shared Function
- make_shared allocates and initializes an object in dynamic memory and returns a shared_ptr that points to that object. When we call make_shared, we must specify the type of object we want to create. make_shared uses its arguments to construct an object of the given type. If we do not pass any arguments, the object is value initialized(§3.3.1).
// p3 points to an int with value 42
shared_ptr<int> p3 = make_shared<int>(42);
// p4 points to a string with value 9999999999
shared_ptr<string> p4 = make_shared<string>(10, '9');
// p5 points to an int that is value initialized(§3.3.1) to 0
shared_ptr<int> p5 = make_shared<int>();
// p6 points to a dynamically allocated, empty vector<string>
auto p6 = make_shared<vector<string>>();
Copying and Assigning shared_ptr s
- shared_ptr has a reference count. The counter is
- incremented when we copy the shared_ptr, use it to initialize another shared_ptr, use it as the right-hand operand of an assignment, or when we pass it to or return it from a function by value.
- decremented when we assign a new value to the shared_ptr and when the shared_ptr itself is destroyed(such as when a local shared_ptr goes out of scope). When the counter goes to zero, the shared_ptr automatically frees the object that it manages.
#include <iostream>
#include <memory>
using std::cout;
using std::shared_ptr;
using std::make_shared;
struct Foo
{
Foo(int num) : num_(num)
{
cout << "Foo(): num_ = " << num_ << '\n';
}
~Foo()
{
cout << "~Foo(): num_ = " << num_ << '\n';
};
int num_;
};
int main()
{
cout << "Begin Test:\n";
shared_ptr<Foo> p1 = make_shared<Foo>(1);
cout << p1.use_count() << '\n'; //1
shared_ptr<Foo> p2(p1);
cout << p1.use_count() << '\n'; //2
cout << p2.use_count() << '\n'; //2
shared_ptr<Foo> p3 = make_shared<Foo>(2);
p2 = p3;
cout << p1.use_count() << '\n'; //1
cout << p2.use_count() << '\n'; //2
cout << p3.use_count() << '\n'; //2
shared_ptr<Foo> p4 = make_shared<Foo>(3);
p1 = p4;
cout << "End Test\n";
return 0;
}
/*
Output:
Begin Test:
Foo(): num_ = 1
1
2
2
Foo(): num_ = 2
1
2
2
Foo(): num_ = 3
~Foo(): num_ = 1
End Test
~Foo(): num_ = 2
~Foo(): num_ = 3
*/
- When the last shared_ptr pointing to an object is destroyed, the shared_ptr class automatically destroys the object to which that shared_ptr points through destructor. The destructor for shared_ptr decrements the reference count of the object to which that shared_ptr points. If the count goes to zero, the shared_ptr destructor destroys the object to which the shared_ptr points and frees the memory used by that object.
- Because memory is not freed until the last shared_ptr goes away, it can be sure that shared_ptrs don’t stay around after they are no longer needed. The program will execute correctly but may waste memory if you neglect to destroy shared_ptrs that the program does not need. shared_ptrs might stay around after you need them if you put shared_ptrs in a container and subsequently reorder the container so that you don’t need all the elements. You should be sure to erase shared_ptr elements once you no longer need those elements.
Classes with Resources That Have Dynamic Lifetime
- Programs use dynamic memory for one of three purposes:
- They don’t know how many objects they’ll need.
- They don’t know the precise type of the objects they need.
- They want to share data between several objects.
- Classes may allocate resources with a lifetime that is independent of the original object. Assume we define Blob class that holds a collection of elements and we want Blob objects that are copies of one another to share the same elements. When two objects share the same underlying data, we can’t destroy the data when only one object goes away.
Blob<string> b1; // empty Blob
{
// new scope
Blob<string> b2 = {"a", "an", "the"};
b1 = b2; // b1 and b2 share the same elements
} // b2 is destroyed, but the elements in b2 must not be destroyed
// b1 points to the elements originally created in b2
Defining the StrBlob Class
- We can’t store the vector directly in a Blob object because members of an object are destroyed when the object itself is destroyed. To ensure that the elements continue to exist, we store the vector in dynamic memory.
- We give each StrBlob a shared_ptr to a dynamically allocated vector. The shared_ptr member keeps track of how many StrBlobs share the same vector and will delete the vector when the last StrBlob using that vector is destroyed.
class StrBlob
{
public:
using size_type = vector<string>::size_type;
StrBlob();
StrBlob(initializer_list<string> il);
size_type size() const
{
return data->size();
}
bool empty() const
{
return data->empty();
}
void push_back(const string &t)
{
data->push_back(t);
}
void pop_back();
string &front();
string &back();
private:
shared_ptr<vector<string>> data;
// throws msg if data[i] isn't valid
void check(size_type i, const string &msg) const;
};
StrBlob Constructors
- Each constructor uses its constructor initializer list(§7.1.4) to initialize its data member to point to a dynamically allocated vector.
- The default constructor allocates an empty vector.
- The constructor that takes an initializer_list passes its parameter to the corresponding vector constructor(§2.2.1). That constructor initializes the vector’s elements by copying the values in the list.
StrBlob::StrBlob() : data(make_shared<vector<string>>()) {}
StrBlob::StrBlob(initializer_list<string> il) :
data(make_shared<vector<string>>(il)) {}
Element Access Members
- The pop_back, front, and back operations access members in the vector. These operations must check that an element exists before attempting to access that element. Because several members need to do the same checking, we use a private function check() that verifies that a given index is in range. check takes a string argument that describes what went wrong and the string will pass to the exception handler.
void StrBlob::check(size_type i, const string &msg) const
{
if(i >= data->size())
{
throw out_of_range(msg);
}
}
string &StrBlob::front()
{
check(0, "front on empty StrBlob");
return data->front();
}
string &StrBlob::back()
{
check(0, "back on empty StrBlob");
return data->back();
}
void StrBlob::pop_back()
{
check(0, "pop_back on empty StrBlob");
data->pop_back();
}
Copying, Assigning, and Destroying StrBlob s
- Copying a shared_ptr increments its reference count.
Assigning one shared_ptr to another increments the count of the right-hand operand and decrements the count in the left-hand operand.
Destroying a shared_ptr decrements the count. If the count in a shared_ptr goes to zero, the object to which that shared_ptr points is automatically destroyed. The vector allocated by the StrBlob constructors will be automatically destroyed when the last StrBlob pointing to that vector is destroyed.
Exercises Section 12.1.1
Exercise 12.1
How many elements do b1 and b2 have at the end of this code?
StrBlob b1;
{
StrBlob b2 = {"a", "an", "the"};
b1 = b2;
b2.push_back("about");
}
- b1: 4; b2: 0
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
class StrBlob
{
public:
using size_type = vector<string>::size_type;
StrBlob(string id) : id_(id), data(make_shared<vector<string>>())
{
cout << "Constructor:\tid = " << id_ << '\n';
}
StrBlob(string id, initializer_list<string> il) :
id_(id), data(make_shared<vector<string>>(il))
{
cout << "Constructor:\tid = " << id_ << '\n';
}
~StrBlob()
{
cout << "Destructor:\tid = " << id_ << '\n';
}
void push_back(const string &t)
{
data->push_back(t);
}
void Print()
{
int length = data->size();
for(int index = 0; index < length; ++index)
{
cout <<(*data)[index] << ' ';
}
cout << '\n';
}
private:
string id_;
shared_ptr<vector<string>> data;
};
int main()
{
cout << "Begin main\n";
StrBlob b1("b1");
{
StrBlob b2("b2", {"a", "an", "the"});
b1 = b2;
b2.push_back("about");
}
b1.Print();// a an the about
//b2.Print();// error: ‘b2’ was not declared in this scope
cout << "End main\n";
return 0;
}
/*
Output:
Begin main
Constructor: id = b1
Constructor: id = b2
Destructor: id = b2
a an the about
End main
Destructor: id = b2
*/
Exercise 12.2
Write your own version of the StrBlob class including the const versions of front and back.
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <exception>
using namespace std;
class StrBlob
{
public:
using size_type = vector<string>::size_type;
StrBlob(string id) : id_(id), data(make_shared<vector<string>>())
{
cout << "Constructor:\tid = " << id_ << '\n';
}
StrBlob(string id, initializer_list<string> il) :
id_(id), data(make_shared<vector<string>>(il))
{
cout << "Constructor:\tid = " << id_ << '\n';
}
~StrBlob()
{
cout << "Destructor:\tid = " << id_ << '\n';
}
string &front()
{
check(0, "front on empty StrBlob");
cout << "string &front()\n";
return data->front();
}
string &back()
{
check(0, "back on empty StrBlob");
cout << "string &back()\n";
return data->back();
}
const string &front() const
{
check(0, "front on empty StrBlob");
cout << "const string &front() const\n";
return data->front();
}
const string &back() const
{
check(0, "back on empty StrBlob");
cout << "const string &back() const\n";
return data->back();
}
private:
string id_;
shared_ptr<vector<string>> data;
// throws msg if data[i] isn't valid
void check(size_type i, const string &msg) const
{
if(i >= data->size())
{
throw out_of_range(msg);
}
}
};
int main()
{
cout << "Begin main\n";
StrBlob obj1("obj1", {"gao", "xiang", "number1"});
cout << obj1.front() << '\t' << obj1.back() << '\n';
obj1.front() = "Gao Xiang";
obj1.back() = "Number1";
cout << obj1.front() << '\t' << obj1.back() << '\n';
StrBlob obj2("obj2", {"hello", "gaoxiang", "number1"});
cout << obj2.front() << '\t' << obj2.back() << '\n';
cout << "End main\n";
return 0;
}
/*
Output:
Begin main
Constructor: id = obj1
string &back()
string &front()
gao number1
string &front()
string &back()
string &back()
string &front()
Gao Xiang Number1
Constructor: id = obj2
string &back()
string &front()
hello number1
End main
Destructor: id = obj2
Destructor: id = obj1
*/
Exercise 12.3
Does this class need const versions of push_back and pop_back? If so, add them. If not, why aren’t they needed?
- It is legal but there seem no logical reason. The compiler doesn’t complain because this doesn’t modify data(which is a pointer) but rather the thing data points to.
Exercise 12.4
In our check function we didn’t check whether i was greater than zero. Why is it okay to omit that check?
- Because the type of i is
std::vector<std::string>::size_type
which is an unsigned. When an argument is negative, it is converted to a positive number.
Exercise 12.5
We did not make the constructor that takes an initializer_list explicit(§7.5.4, p. 296). Discuss the pros and cons of this design choice.
- Pros: The compiler will not use this constructor in an automatic conversion and we can realize clearly which class we have used.
- Cons: We must uses the constructor to construct a temporary StrBlob object. We cannot use the copy form of initialization.
12.1.2. Managing Memory Directly
- The new operator allocates memory, and delete frees memory allocated by new. Classes that do manage their own memory, unlike those that use smart pointers, cannot rely on the default definitions for the members that copy, assign, and destroy class objects(§7.1.4).
Using new to Dynamically Allocate and Initialize Objects
- new constructs an object on the free store and returns a pointer to that object. By default, dynamically allocated objects are default initialized(§2.2.1): objects of built-in or compound type have undefined value; objects of class type are initialized by their default constructor. We can value initialize(§3.3.1, p. 98) them by following the type name with a pair of empty parentheses.
int *pi1 = new int; // default initialized; *pi1 is undefined
int *pi2 = new int(); // value initialized to 0; *pi2 is 0
- For class types that define their own constructors, requesting value initialization has no effect: the object is initialized by the default constructor.
A value-initialized object of built-in type has a well-defined value but a default-initialized object does not.
Members of built-in type in classes that rely on the synthesized default constructor will also be uninitialized if those members are not initialized in the class body(§7.1.4). - We can initialize them using direct initialization(§3.2.1): use traditional construction with parentheses; under C++11, use list initialization with curly braces.
string *ps = new string(10, '9'); // *ps is "9999999999"
// vector with ten elements with values from 0 to 9
vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9};
- When we provide a single initializer inside parentheses, we can use auto(§2.5.2) to deduce the type of the object we want to allocate from that initializer.
auto p1 = new auto(obj); // p points to an object of the type of obj
// that object is initialized from obj
auto p2 = new auto{a,b,c}; // error: must use parentheses for the initializer
Dynamically Allocated const Objects
// allocate and initialize a const int
const int *pci = new const int(1024);
// allocate a default-initialized const empty string
const string *pcs = new const string;
- A dynamically allocated const object must be initialized. A const dynamic object of a class type that defines a default constructor(§7.1.4) may be initialized implicitly; objects of other types must be explicitly initialized.
Memory Exhaustion
- By default, if new is unable to allocate the requested storage, it throws an exception of type bad_alloc(§5.6). We can prevent new from throwing an exception by using
new(nothrow)
.
int *p1 = new int; // if allocation fails, new throws std::bad_alloc
int *p2 = new(nothrow) int; // if allocation fails, new returns a null pointer
- This form of new is referred to as placement new that lets us pass additional arguments to new. In this case, we pass an object named nothrow that is defined by the library.
- When we pass nothrow to new, we tell new that it must not throw an exception. If this form of new is unable to allocate the requested storage, it will return a null pointer. Both bad_alloc and nothrow are defined in the new header.
Freeing Dynamic Memory
- We return dynamically allocated memory to the system through a delete expression which takes a pointer that must point to a dynamically allocated object we want to free or be null.
- A delete expression performs two actions: destroys the object to which its given pointer points, and frees the corresponding memory.
Pointer Values and delete
- The pointer we pass to delete must either point to dynamically allocated memory or be a null pointer. Deleting a pointer to memory that was not allocated by new, or deleting the same pointer value more than once, is undefined.
- Although the value of a const object cannot be modified, the object itself can be destroyed. A const dynamic object is freed by executing delete on a pointer that points to that object:
const int *pci = new const int(1024);
delete pci; // ok: deletes a const object
Dynamically Allocated Objects Exist until They Are Freed
- A dynamic object managed through a built-in pointer exists until it is explicitly deleted. Functions that return pointers to dynamic memory put a burden on their callers, the caller must remember to delete the memory.
Foo* factory(T arg)
{
// process arg as appropriate
return new Foo(arg); // caller is responsible for deleting this memory
}
- All too often the caller forgets to do so:
void use_factory(T arg)
{
Foo *p = factory(arg);
// use p but do not delete it
}// p goes out of scope, but the memory to which p points is not freed!
- p was the only pointer to the memory allocated by factory. Once use_factory returns, the program has no way to free that memory.
Caution: Managing Dynamic Memory Is Error-Prone
- There are three common problems with using new and delete to manage dynamic memory:
- Forgetting to delete memory(memory leak). Memory leaks usually cannot be detected until the application is run for a long enough time to actually exhaust memory.
- Using an object after it has been deleted. This error can be detected by making the pointer null after the delete.
- Deleting the same memory twice. This error can happen when two pointers address the same dynamically allocated object. If delete is applied to one of the pointers, then the object’s memory is returned to the free store. If we subsequently delete the second pointer, then the free store may be corrupted.
Resetting the Value of a Pointer after a delete …
- When we delete a pointer, that pointer becomes invalid but it continues to hold the address of the freed dynamic memory. After the delete, the pointer becomes a dangling pointer that refers to memory which once held an object but no longer does so.
- Dangling pointers have all the problems of uninitialized pointers(§2.3.2). We can avoid the problems with dangling pointers by deleting the memory associated with a pointer just before the pointer itself goes out of scope. So, there is no chance to use the pointer after the memory associated with the pointer is freed. If we need to keep the pointer around, we can assign nullptr to the pointer after we use delete to make it clear that the pointer points to no object.
…Provides Only Limited Protection
- There can be several pointers that point to the same memory. Resetting the pointer we use to delete that memory lets us check that particular pointer but has no effect on any of the other pointers that still point at the freed memory.
int *p(new int(42)); // p points to dynamic memory
auto q = p; // p and q point to the same memory
delete p; // invalidates both p and q
p = nullptr; // indicates that p is no longer bound to an object
- Resetting p has no effect on q, which became invalid when we deleted the memory to which p(and q) pointed.
Exercises Section 12.1.2
Exercise 12.6
Write a function that returns a dynamically allocated vector of ints. Pass that vector to another function that reads the standard input to give values to the elements. Pass the vector to another function to print the values that were read. Remember to delete the vector at the appropriate time.
#include <iostream>
#include <string>
#include <vector>
using namespace std;
vector<int> *Allocate()
{
return new vector<int>();
}
void Input(vector<int> *vec)
{
int data;
while(cin >> data)
{
vec->push_back(data);
}
}
void Output(vector<int> *vec)
{
int length = vec->size();
for(int index = 0; index < length; ++index)
{
cout <<(*vec)[index] << ' ';
}
cout << '\n';
}
int main()
{
vector<int> *vec = Allocate();
Input(vec);
Output(vec);
delete vec;
return 0;
}
Exercise 12.7
Redo the previous exercise, this time using shared_ptr.
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
shared_ptr<vector<int>> Allocate()
{
return make_shared<vector<int>>();
}
void Input(shared_ptr<vector<int>> vec)
{
int data;
while(cin >> data)
{
vec->push_back(data);
}
}
void Output(shared_ptr<vector<int>> vec)
{
int length = vec->size();
for(int index = 0; index < length; ++index)
{
cout <<(*vec)[index] << ' ';
}
cout << '\n';
}
int main()
{
shared_ptr<vector<int>> vec = Allocate();
Input(vec);
Output(vec);
return 0;
}
Exercise 12.8
Explain what if anything is wrong with the following function.
bool b()
{
int* p = new int;
// ...
return p;
}
- The pointer will be converted to a bool; memory leak.
Exercise 12.9
Explain what happens in the following code:
int *q = new int(42), *r = new int(100);
r = q;
auto q2 = make_shared<int>(42), r2 = make_shared<int>(100);
r2 = q2;
- For q and r: Memory leakage happens. Because after r = q was executed, no pointer points to the int r had pointed to. It implies that no chance to free the memory for it.
- For q2 and r2: It’s safe. Because after ‘r2 = q2’, the reference count belongs to r2 reduce to 0 and the reference count belongs to q2 increase to 2, then the memory allocated by r2 will be released automatically.
12.1.3. Using shared_ptrs with new
- If we do not initialize a smart pointer, it is initialized as a null pointer. We can initialize a smart pointer from a pointer returned by new. The smart pointer constructors that take pointers are explicit(§7.5.4), we must use the direct form of initialization(§3.2.1) to initialize a smart pointer.
shared_ptr<int> p1 = new int(1024); // error: must use direct initialization
shared_ptr<int> p2(new int(1024)); // ok: uses direct initialization
- A function that returns a shared_ptr cannot implicitly convert a plain pointer in its return statement. We must explicitly bind a shared_ptr to the pointer we want to return.
shared_ptr<int> clone(int p)
{
return new int(p); // error
return shared_ptr<int>(new int(p)); // ok
}
- By default, a pointer used to initialize a smart pointer must point to dynamic memory because, by default, smart pointers use delete to free the associated object. We can bind smart pointers to pointers to other kinds of resources, to do so, we must supply our own operation to use in place of delete(§12.1.4).
Don’t Mix Ordinary Pointers and Smart Pointers …
- A shared_ptr can coordinate destruction only with other shared_ptrs that are copies of itself.
// ptr is created and initialized when process is called
void process(shared_ptr<int> ptr)
{
// use ptr
} // ptr goes out of scope and is destroyed
- The parameter to process is passed by value(copied into ptr), so, the reference count of ptr inside process is at least 2. When process completes, the reference count of ptr is decremented but cannot go to zero. Therefore, when the local variable ptr is destroyed, the memory to which ptr points will not be deleted. The right way to use this function is to pass it a shared_ptr.
shared_ptr<int> p(new int(42)); // reference count is 1
process(p); // in process the reference count is 2
int i = *p; // ok: reference count is 1
- Although we cannot pass a built-in pointer to process, we can pass process a temporary shared_ptr that we explicitly construct from a built-in pointer. But doing so is an error.
int *x(new int(1024)); // x is a plain pointer
process(x); // error: cannot convert int* to shared_ptr<int>
process(shared_ptr<int>(x)); // legal, but the memory will be deleted.
int j = *x; // undefined: x is a dangling pointer.
- We pass a temporary shared_ptr to process and it is destroyed when the expression in which the call appears finishes. Destroying the temporary decrements the reference count, which goes to zero. The memory to which the temporary points is freed when the temporary is destroyed. But x continues to point to that freed memory, x is now a dangling pointer.
- When we bind a shared_ptr to a plain pointer, we give responsibility for that memory to that shared_ptr. Once we give shared_ptr responsibility for a pointer, we should no longer use a built-in pointer to access the memory to which the shared_ptr points because we may not know when that object is destroyed.
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
void process(shared_ptr<int> ptr)
{
cout << "ptr =\t" << ptr << ": reference_count = " << ptr.use_count() << '\n';
}
int main()
{
shared_ptr<int> ptr1(new int(1));
cout << "ptr1 =\t" << ptr1 << ": reference_count = " << ptr1.use_count() << '\n';
process(ptr1);
cout << "After process: *ptr1 =\t" << *ptr1 <<
": reference_count = " << ptr1.use_count() << '\n';
int *num = new int(7188);
cout << "Before process: *num =\t" << *num << '\n';
process(shared_ptr<int>(num));
cout << "After process: *num =\t" << *num << '\n';
return 0;
}
/*
Output:
ptr1 = 0x130e010: reference_count = 1
ptr = 0x130e010: reference_count = 2
After process: *ptr1 = 1: reference_count = 1
Before process: *num = 7188
ptr = 0x130e050: reference_count = 1
After process: *num = 0
*/
…and Don’t Use get to Initialize or Assign Another Smart Pointer
- The smart pointer types define function get(Table 12.1) that returns a built-in pointer to the object that the smart pointer is managing. This function is intended for cases when we need to pass a built-in pointer to code that can’t use a smart pointer. The code that uses the return from get must not delete that pointer.
- Though the compiler will not complain, it is an error to bind another smart pointer to the pointer returned by get.
shared_ptr<int> p(new int(42)); // reference count is 1
int *q = p.get(); // ok: but don't use q in any way that might delete its pointer
{
// new block. undefined: two independent shared_ptrs point to the same memory
shared_ptr<int>(q);
} // block ends, q is destroyed, and the memory to which q points is freed
int foo = *p; // undefined; the memory to which p points was freed
- Both p and q point to the same memory. Because they were created independently from each other, each has a reference count of 1. When the block in which q was defined ends, q is destroyed. Destroying q frees the memory to which q points. That makes p into a dangling pointer, meaning that what happens when we attempt to use p is undefined. When p is destroyed, the pointer to that memory will be deleted a second time.
- Use get only to pass access to the pointer to code that you know will not delete the pointer.Never use get to initialize or assign to another smart pointer.
Other shared_ptr Operations
- We can use reset to assign a new pointer to a shared_ptr.
p = new int(1024); // error: cannot assign a pointer to a shared_ptr
p.reset(new int(1024)); // ok: p points to a new object
- reset updates the reference counts and, if appropriate, deletes the object to which p points. The reset member is often used together with unique to control changes to the object shared among several shared_ptrs. Before changing the underlying object, we check whether we’re the only user. If not, we make a new copy before making the change.
if(!p.unique())
{
p.reset(new string(*p)); // we aren't alone; allocate a new copy
}
*p += new_val; // now we're the only pointer, ok to change this object
Exercises Section 12.1.3
Exercise 12.10
Explain whether the following call to the process function defined on page 464 is correct. If not, how would you correct the call?
shared_ptr<int> p(new int(42));
process(shared_ptr<int>(p));
#include <iostream>
#include <memory>
using namespace std;
void process(shared_ptr<int> ptr)
{
cout << "ptr =\t" << ptr << ": reference_count = " << ptr.use_count() << '\n';
}
int main()
{
shared_ptr<int> p(new int(1));
process(shared_ptr<int>(p)); // ptr = 0xc7d010: reference_count = 2
return 0;
}
Exercise 12.11
What would happen if we called process as follows?
process(shared_ptr<int>(p.get()));
- When the call process ends, p is a dangling pointer. When p is destroyed, the pointer to that memory will be deleted a second time.
Exercise 12.12
Using the declarations of p and sp explain each of the following calls to process. If the call is legal, explain what it does. If the call is illegal, explain why:
auto p = new int();
auto sp = make_shared<int>();
(a) process(sp);
(b) process(new int());
(c) process(p);
(d) process(shared_ptr<int>(p));
- Legal.
- Illegal: can’t implicitly convert
int*
to shared_ptr. - Illegal, same as above.
- Legal: create a temporary object.
Exercise 12.13
What happens if we execute the following code?
auto sp = make_shared<int>();
auto p = sp.get();
delete p;
*** Error in `./a.out': free(): invalid pointer: 0x0000000001670028 ***
Aborted
12.1.4. Smart Pointers and Exceptions
- When we use a smart pointer, the smart pointer class ensures that memory is freed when it is no longer needed even if the block is exited prematurely.
void f()
{
shared_ptr<int> sp(new int(42)); // allocate a new object
// code that throws an exception that is not caught inside f
} // shared_ptr freed automatically when the function ends
- When a function is exited, whether through normal processing or due to an exception, all the local objects are destroyed. sp is a shared_ptr, so destroying sp checks its reference count. Since sp is the only pointer to the memory it manages, that memory will be freed as part of destroying sp.
- In contrast, memory that we manage directly is not automatically freed when an exception occurs. If we use built-in pointers to manage memory and an exception occurs after a new but before the corresponding delete, then that memory won’t be freed.
void f()
{
int *ip = new int(42); // dynamically allocate a new object
// code that throws an exception that is not caught inside f
delete ip; // free the memory before exiting
}
- If an exception happens between the new and the delete, and is not caught inside f, then this memory can never be freed because there is no pointer to this memory outside the function f.
Smart Pointers and Dumb Classes
- Many C++ classes define destructors(§12.1.1) that take care of cleaning up the resources used by that object. Classes that are designed to be used by both C and C++ generally require the user to specifically free any resources that are used.
- We can use the same techniques we use to manage dynamic memory to manage classes that do not have well-behaved destructors. Suppose we use a network library that is used by both C and C++. Programs that use this library might contain code such as
struct destination; // represents what we are connecting to
struct connection; // information needed to use the connection
connection connect(destination*); // open the connection
void disconnect(connection); // close the given connection
void f(destination &d /* other parameters */)
{
// get a connection; must remember to close it when done
connection c = connect(&d);
// use the connection
// if we forget to call disconnect before exiting f, there will be no way to close c
}
- If connection had a destructor, that destructor would automatically close the connection when f completes. But connection does not have a destructor.
Using Our Own Deletion Code
- By default, shared_ptrs point to dynamic memory and when a shared_ptr is destroyed, it executes delete on the pointer it holds. To use a shared_ptr to manage a connection, we must first define a function to use in place of delete. It must be possible to call this deleter function with the pointer stored inside the shared_ptr. In this case, our deleter must take a single argument of type connection*:
void end_connection(connection *p)
{
disconnect(*p);
}
- When we create a shared_ptr, we can pass an optional argument that points to a deleter function:
void f(destination &d /* other parameters */)
{
connection c = connect(&d);
shared_ptr<connection> p(&c, end_connection);
// use the connection
// when f exits, even if by an exception, the connection will be properly closed
}
- When p is destroyed, it will call end_connection on that pointer. In turn, end_connection will call disconnect, thus ensuring that the connection is closed. When f exits normally or an exception occurs, p will be destroyed and the connection will be closed.
Caution: Smart Pointer Pitfalls
- Conventions to use smart pointers correctly:
- Don’t use the same built-in pointer value to initialize(or reset) more than one smart pointer.
- Don’t delete the pointer returned from get().
- Don’t use get() to initialize or reset another smart pointer.
- If you use a pointer returned by get(), remember that the pointer will become invalid when the last corresponding smart pointer goes away.
- If you use a smart pointer to manage a resource other than memory allocated by new, remember to pass a deleter(§12.1.4 and §12.1.5).
Exercises Section 12.1.4
Exercise 12.14
Write your own version of a function that uses a shared_ptr to manage a connection.
#include <iostream>
#include <string>
#include <memory>
using namespace std;
struct connection
{
string ip;
int port;
connection(string ip_, int port_) : ip(ip_), port(port_) {}
};
struct destination
{
string ip;
int port;
destination(string ip_, int port_) : ip(ip_), port(port_) {}
};
connection connect(destination* pDest)
{
shared_ptr<connection> pConn(new connection(pDest->ip, pDest->port));
cout << "creating connection(" << pConn.use_count() << ")\n";
return *pConn;
}
void disconnect(connection pConn)
{
cout << "connection close(" << pConn.ip << ":" << pConn.port << ")\n";
}
void end_connection(connection* pConn)
{
disconnect(*pConn);
}
void f(destination& d)
{
connection conn = connect(&d);
shared_ptr<connection> p(&conn, end_connection);
cout << "connecting now(" << p.use_count() << ")\n";
}
int main()
{
destination dest("127.0.0.1", 7188);
f(dest);
return 0;
}
/*
Output:
creating connection(1)
connecting now(1)
connection close(127.0.0.1:7188)
*/
Exercise 12.15
Rewrite the first ###Exercise to use a lambda(§10.3.2, p. 388) in place of the end_connection function.
#include <iostream>
#include <string>
#include <memory>
using namespace std;
struct connection
{
string ip;
int port;
connection(string ip_, int port_):ip(ip_), port(port_) { }
};
struct destination
{
string ip;
int port;
destination(string ip_, int port_):ip(ip_), port(port_) { }
};
connection connect(destination* pDest)
{
shared_ptr<connection> pConn(new connection(pDest->ip, pDest->port));
cout << "creating connection(" << pConn.use_count() << ")\n";
return *pConn;
}
void disconnect(connection pConn)
{
cout << "connection close(" << pConn.ip << ":" << pConn.port << ")\n";
}
void f(destination &d)
{
connection conn = connect(&d);
shared_ptr<connection> p(&conn, [](connection *p)
{
disconnect(*p);
});
cout << "connecting now(" << p.use_count() << ")\n";
}
int main()
{
destination dest("127.0.0.1", 7188);
f(dest);
}
/*
Output:
creating connection(1)
connecting now(1)
connection close(127.0.0.1:7188)
*/
12.1.5. unique_ptr
- Only one unique_ptr at a time can point to a given object. The object to which a unique_ptr points is destroyed when the unique_ptr is destroyed. Table 12.1 & 12.4
- When we define a unique_ptr, we bind it to a pointer returned by new and we must use the direct form of initialization.
unique_ptr<double> p1; // unique_ptr that can point at a double; p1 is nullptr.
unique_ptr<int> p2(new int(42)); // p2 points to int with value 42
- Because a unique_ptr owns the object to which it points, unique_ptr does not support copy or assignment.
unique_ptr<string> p1(new string("Stegosaurus"));
unique_ptr<string> p2(p1); // error: no copy for unique_ptr
unique_ptr<string> p3 = p2; // error: no assign for unique_ptr
- We can transfer ownership from one(nonconst) unique_ptr to another by calling release or reset.
// transfers ownership from p1(which points to the string Stegosaurus) to p2
unique_ptr<string> p2(p1.release()); // release makes p1 null
unique_ptr<string> p3(new string("hello"));
// transfers ownership from p3 to p2
p2.reset(p3.release()); // reset deletes the memory to which p2 had pointed
- The release member returns the pointer currently stored in the unique_ptr and makes that unique_ptr null. p2 is initialized from the pointer value that had been stored in p1 and p1 becomes null.
- The reset member takes an optional pointer and repositions the unique_ptr to point to the given pointer. If the unique_ptr is not null, then the object to which the unique_ptr had pointed is deleted. The call to reset on p2 frees the memory used by the string initialized from “Stegosaurus”, transfers p3’s pointer to p2, and makes p3 null.
- The pointer returned by release usually is used to initialize or assign another smart pointer. So, responsibility for managing the memory is transferred from one smart pointer to another. But if we do not use another smart pointer to hold the pointer returned from release, our program takes over responsibility for freeing that resource:
p2.release(); // WRONG: p2 won't free the memory and we've lost the pointer
auto p = p2.release(); // ok, but we must remember to delete(p)
Passing and Returning unique_ptr s
- One exception that we cannot copy a unique_ptr: We can copy or assign a unique_ptr that is about to be destroyed. The most common example is when we return a unique_ptr from a function:
unique_ptr<int> clone(int p)
{
// ok: explicitly create a unique_ptr<int> from int*
return unique_ptr<int>(new int(p));
}
- We can also return a copy of a local object:
unique_ptr<int> clone(int p)
{
unique_ptr<int> ret(new int(p));
// . . .
return ret;
}
- In both cases, the compiler knows that the object being returned is about to be destroyed and it does a special kind of “copy”(§13.6.2).
Passing a Deleter to unique_ptr
- By default, unique_ptr uses delete to free the object to which a unique_ptr points. Overridding the deleter: We must supply the deleter type inside the angle brackets along with the type to which the unique_ptr can point. We supply a callable object of the specified type when we create or reset an object of this type.
// p points to an object of type OBJT and uses an object of type DELT to free that object
// it will call an object named fcn of type DELT
unique_ptr<OBJT, DELT> p(new OBJT, fcn);
void f(destination &d /* other needed parameters */)
{
connection c = connect(&d); // open the connection
// when p is destroyed, the connection will be closed
unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);
// use the connection
// when f exits, even if by an exception, the connection will be properly closed
}
- Because decltype(end_connection) returns a function type, we must add a * to indicate that we’re using a pointer to that type.
Exercises Section 12.1.5
Exercise 12.16
Compilers don’t always give easy-to-understand error messages if we attempt to copy or assign a unique_ptr. Write a program that contains these errors to see how your compiler diagnoses them.
- error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = double; _Dp = std::default_delete]’
Exercise 12.17
Which of the following unique_ptr declarations are illegal or likely to result in subsequent program error? Explain what the problem is with each one.
int ix = 1024, *pi = &ix, *pi2 = new int(2048);
typedef unique_ptr<int> Int;
(a) Int p0(ix);
(b) Int p1(pi);
(c) Int p2(pi2);
(d) Int p3(&ix);
(e) Int p4(new int(2048));
(f) Int p5(p2.get());
error: invalid conversion from ‘int’ to ‘std::unique_ptr<int>::pointer {aka int*}’
*** Error in
cpp’: free(): invalid pointer: 0x00007ffec78f8e9c * Aborted`
The code can compile, but will cause error at run time. The reason is that when the unique_ptr p1 is out of scope, delete will be called to free the object. But the object is not allocate using new. Thus, an error would be thrown by operating system.- This code can compile, but cause a dangling pointer at run time. The reason is that the unique_ptr will free the object the raw pointer is pointing to.
- Same as p1.
- ok
*** Error in
cpp’: double free or corruption: 0x00000000020ec010 * Aborted`
Two unique_ptrs are pointing to the same object, when both are out of scope, operating system will throw double free or corruption.
Exercise 12.18
Why doesn’t shared_ptr have a release member?
- Because other shared_ptr that points the same object can still delete this object.Thus, it’s meaningless to provide this member
http://stackoverflow.com/questions/1525764/how-to-release-pointer-from-boostshared-ptr
12.1.6. weak_ptr
- A weak_ptr(Table 12.5) is a smart pointer that points to an object which is managed by a shared_ptr. Binding a weak_ptr to a shared_ptr does not change the reference count of that shared_ptr. Once the last shared_ptr pointing to the object goes away, the object itself will be deleted even if there are weak_ptrs pointing to it.
- We cannot use a weak_ptr to access its object directly, instead we must call lock. The lock function checks whether the object to which the weak_ptr points still exists. If so, lock returns a shared_ptr to the shared object.
#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
shared_ptr<int> p1 = make_shared<int>(1);
cout << p1.use_count() << '\n';
weak_ptr<int> wp(p1);
cout << p1.use_count() << '\t' << wp.use_count() << '\n';
if(shared_ptr<int> p2 = wp.lock())
{
cout << p1.use_count() << '\t' << wp.use_count() << '\t'
<< p2.use_count() << '\n';
}
cout << p1.use_count() << '\t' << wp.use_count() << '\n';
return 0;
}
/*
Output:
1
1 1
2 2 2
1 1
*/
Checked Pointer Class
- We define a companion pointer class StrBlobPtr that will store a weak_ptr to the data member of the StrBlob from which it was initialized. By using a weak_ptr, we don’t affect the lifetime of the vector to which a given StrBlob points and we can prevent the user from attempting to access a vector that no longer exists.
- StrBlobPtr will have two data members: wptr, which is either null or points to a vector in a StrBlob; and curr, which is the index of the element that this object currently denotes.
// StrBlobPtr throws an exception on attempts to access a nonexistent element
class StrBlobPtr
{
public:
StrBlobPtr() : curr(0) {}
StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}
string &deref() const;
StrBlobPtr &incr(); // prefix version
private:
// check returns a shared_ptr to the vector if the check succeeds
shared_ptr<vector<string>> check(size_t, const string&) const;
// store a weak_ptr, which means the underlying vector might be destroyed
weak_ptr<vector<string>> wptr;
size_t curr; // current position within the array
};
- The default constructor generates a null StrBlobPtr, its constructor initializer list initializes curr to zero and implicitly initializes wptr as a null weak_ptr.
The second constructor takes a reference to StrBlob and an optional index value. This constructor initializes wptr to point to the vector in the shared ptr of the given object and initializes curr to the value of sz. - The check member must check whether the vector to which it points is still around.
shared_ptr<vector<string>> check(size_t i, const string &msg) const
{
shared_ptr<vector<string>> ret = wptr.lock();
if(!ret)
{
throw runtime_error("unbound StrBlobPtr");
}
if(i >= ret->size())
{
throw out_of_range(msg);
}
return ret;
}
Pointer Operations
string &deref() const
{
shared_ptr<vector<string>> p = check(curr, "dereference past end");
return(*p)[curr];
}
// prefix: return a reference to the incremented object
StrBlobPtr &incr()
{
check(curr, "increment past end of StrBlobPtr");
++curr;
return *this;
}
- In order to access the data member, our pointer class will have to be a friend of StrBlob. We also give StrBlob class begin and end operations that return a StrBlobPtr pointing to itself.
##Exercises Section 12.1.6
###Exercise 12.19
>Define your own version of StrBlobPtr and update your StrBlob class with the appropriate friend declaration and begin and end members.
```cpp
<div class="se-preview-section-delimiter"></div>
#include <iostream>
<div class="se-preview-section-delimiter"></div>
#include <string>
<div class="se-preview-section-delimiter"></div>
#include <vector>
<div class="se-preview-section-delimiter"></div>
#include <memory>
using namespace std;
class StrBlobPtr;
class StrBlob
{
friend class StrBlobPtr;
public:
StrBlobPtr begin();
StrBlobPtr end();
typedef vector<string>::size_type size_type;
StrBlob(string id) : id_(id), data(make_shared<vector<string>>())
{
cout << "Constructor:\tid = " << id_ << '\n';
}
StrBlob(string id, initializer_list<string> il) :
id_(id), data(make_shared<vector<string>>(il))
{
cout << "Constructor:\tid = " << id_ << '\n';
}
~StrBlob()
{
cout << "Destructor:\tid = " << id_ << '\n';
}
size_type size() const
{
return data->size();
}
bool empty() const
{
return data->empty();
}
void push_back(const string &t)
{
data->push_back(t);
}
void pop_back()
{
check(0, "pop_back on empty StrBlob");
data->pop_back();
}
string &front()
{
check(0, "front on empty StrBlob");
return data->front();
}
string &back()
{
check(0, "back on empty StrBlob");
return data->back();
}
const string &front() const
{
check(0, "front on empty StrBlob");
return data->front();
}
const string &back() const
{
check(0, "back on empty StrBlob");
return data->back();
}
void Print()
{
int length = data->size();
for(int index = 0; index < length; ++index)
{
cout <<(*data)[index] << ' ';
}
cout << '\n';
}
//private:
string id_;
shared_ptr<vector<string>> data;
// throws msg if data[i] isn't valid
void check(size_type i, const string &msg) const
{
if(i >= data->size())
{
throw out_of_range(msg);
}
}
};
// StrBlobPtr throws an exception on attempts to access a nonexistent element
class StrBlobPtr
{
public:
StrBlobPtr() : curr(0) {}
StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}
string &deref() const
{
shared_ptr<vector<string>> p = check(curr, "dereference past end");
return(*p)[curr];
}
// prefix: return a reference to the incremented object
StrBlobPtr &incr()
{
check(curr, "increment past end of StrBlobPtr");
++curr;
return *this;
}
private:
// check returns a shared_ptr to the vector if the check succeeds
shared_ptr<vector<string>> check(size_t i, const string &msg) const
{
shared_ptr<vector<string>> ret = wptr.lock();
if(!ret)
{
throw runtime_error("unbound StrBlobPtr");
}
if(i >= ret->size())
{
throw out_of_range(msg);
}
return ret;
}
// store a weak_ptr, which means the underlying vector might be destroyed
weak_ptr<vector<string>> wptr;
size_t curr; // current position within the array
};
StrBlobPtr StrBlob::begin()
{
return StrBlobPtr(*this);
}
StrBlobPtr StrBlob::end()
{
StrBlobPtr ret = StrBlobPtr(*this, data->size());
return ret;
}
int main()
{
StrBlob obj1("obj1");
StrBlobPtr obj2(obj1);
return 0;
}
<div class="se-preview-section-delimiter"></div>
Exercise 12.20
Write a program that reads an input file a line at a time into a StrBlob and uses a StrBlobPtr to print each element in that StrBlob.
int main()
{
ifstream ifs("data.txt");
StrBlob obj("obj");
for(string str; getline(ifs, str); )
{
obj.push_back(str);
}
for(StrBlobPtr pbeg(obj.begin()), pend(obj.end()); pbeg != pend; pbeg.incr())
{
cout << pbeg.deref() << endl;
}
}
/*
Output:
Constructor: id = obj
0-201-78345-X 3 20
0-201-78345-X 2 25
0-201-78346-X 1 100
0-201-78346-X 2 99.9
0-201-78346-X 6 89.9
Destructor: id = obj
*/
<div class="se-preview-section-delimiter"></div>
Exercise 12.21
We could have written StrBlobPtr ’s deref member as follows.
std::string& deref() const
{
return(*check(curr, "dereference past end"))[curr];
}
<div class="se-preview-section-delimiter"></div>
Which version do you think is better and why?
- The original one is better, because it’s more readable.
Exercise 12.22
What changes would need to be made to StrBlobPtr to create a class that can be used with a const StrBlob? Define a class named ConstStrBlobPtr that can point to a const StrBlob.
<div class="se-preview-section-delimiter"></div>
#include <iostream>
<div class="se-preview-section-delimiter"></div>
#include <string>
<div class="se-preview-section-delimiter"></div>
#include <vector>
<div class="se-preview-section-delimiter"></div>
#include <memory>
using namespace std;
class ConstStrBlobPtr;
class StrBlob
{
friend class ConstStrBlobPtr;
public:
ConstStrBlobPtr begin() const; // NOTE: ADD const
ConstStrBlobPtr end() const; // NOTE: ADD const
};
class ConstStrBlobPtr
{
public:
ConstStrBlobPtr() : curr(0) {}
// NOTE: ADD const
ConstStrBlobPtr(const StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}
// NOTE: ADD const
const string &deref() const
{
shared_ptr<vector<string>> p = check(curr, "dereference past end");
return(*p)[curr];
}
};
ConstStrBlobPtr StrBlob::begin() const // NOTE: ADD const
{
return ConstStrBlobPtr(*this);
}
ConstStrBlobPtr StrBlob::end() const // NOTE: ADD const
{
ConstStrBlobPtr ret = ConstStrBlobPtr(*this, data->size());
return ret;
}
int main()
{
const StrBlob obj("obj");
ConstStrBlobPtr obj2(obj);
}
<div class="se-preview-section-delimiter"></div>
12.2. Dynamic Arrays
- C++ provide two ways to allocate an array of objects at once.
- A second kind of new expression that allocates and initializes an array of objects.
- A template class named allocator that lets us separate allocation from initialization. Using an allocator generally provides better performance and more flexible memory management(explain in §12.2.2).
- Most applications should use a library container rather than dynamically allocated arrays. Using a container is easier, less likely to contain memory-management bugs, and is likely to give better performance.
12.2.1. new and Arrays
- We ask new to allocate an array of objects by specifying the number of objects to allocate in a pair of square brackets after a type name. new allocates the requested number of objects and returns a pointer to the first one.
int *p = new int[get_size()]; // p points to the first of these ints
<div class="se-preview-section-delimiter"></div>
- The size inside the brackets must have integral type but need not be a constant. We can also allocate an array by using a type alias to represent an array type.
typedef int arr[42]; // arr names the type array of 42 ints
int *p = new arr; // same as: int *p = new int[42]; p points to the first one
<div class="se-preview-section-delimiter"></div>
Allocating an Array Yields a Pointer to the Element Type
- Because the allocated memory does not have an array type(only a pointer), we cannot use begin, end(§3.5.3) or a range for on a dynamic array.
Initializing an Array of Dynamically Allocated Objects
- By default, objects allocated by new, whether allocated as a single object or in an array, are default initialized. We can value initialize(§3.3.1) the elements in an array by following the size with an empty pair of parentheses.
int *pi = new int[10]; // block of ten uninitialized ints
int *pi2 = new int[10](); // block of ten ints value initialized to 0
string *ps = new string[10]; // block of ten empty strings
string *ps2 = new string[10](); // block of ten empty strings
<div class="se-preview-section-delimiter"></div>
- Under C++11, we can provide a braced list of element initializers:
// block of ten ints each initialized from the corresponding initializer
int *pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9};
// block of ten strings; the first four are initialized from the given initializers
// remaining elements are value initialized
string *psa3 =new string[10]{"a", "an", "the", string(3,'x')};
<div class="se-preview-section-delimiter"></div>
- As when we list initialize an object of built-in array type(§3.5.1), the initializers are used to initialize the first elements in the array.
- If there are fewer initializers than elements, the remaining elements are value initialized.
- If there are more initializers than the given size, then the new expression fails and no storage is allocated. new throws an exception of type bad_array_new_length that is defined in .
- Although we can use empty parentheses to value initialize the elements of an array, we cannot supply an element initializer inside the parentheses which means that we cannot use auto to allocate an array(§12.1.2).
It Is Legal to Dynamically Allocate an Empty Array
- Calling new[n] with n equal to 0 is legal even though we cannot create an array variable of size 0.
char arr[0]; // error: cannot define a zero-length array
char *cp = new char[0]; // ok: but cp can't be dereferenced
<div class="se-preview-section-delimiter"></div>
- When we use new to allocate an array of size zero, new returns a valid, nonzero pointer. This pointer acts as the off-the-end pointer(§3.5.3) for a zero-element array. We can use this pointer in ways that we use an off-the-end iterator. The pointer can be compared as in the loop. We can add zero to(or subtract zero from) such a pointer and can subtract the pointer from itself, yielding zero. The pointer cannot be dereferenced because it points to no element.
Freeing Dynamic Arrays
- To free a dynamic array, we use a delete with an empty pair of square brackets.
delete p; // p must point to a dynamically allocated object or be null
delete [] pa; // pa must point to a dynamically allocated array or be null
<div class="se-preview-section-delimiter"></div>
- The second statement destroys the elements in the array to which pa points and frees the corresponding memory. Elements in an array are destroyed in reverse order: the last element is destroyed first, then the second to last, and so on.
- When we delete a pointer to an array, the empty bracket pair is essential: It indicates to the compiler that the pointer addresses the first element of an array of objects. If we omit the brackets when we delete a pointer to an array(or provide them when we delete a pointer to an object), the behavior is undefined.
- When we use a type alias that defines an array type, we can allocate an array without using [] with new. But we must use brackets when we delete a pointer to that array:
typedef int arr[42]; // arr names the type array of 42 ints
int *p = new arr; // allocates an array of 42 ints; p points to the first one
delete [] p; // brackets are necessary because we allocated an array
<div class="se-preview-section-delimiter"></div>
Smart Pointers and Dynamic Arrays
- The library provides a version of unique_ptr that can manage arrays allocated by new. To use a unique_ptr to manage a dynamic array, we must include a pair of empty brackets after the object type.
// up points to an array of ten uninitialized ints
unique_ptr<int[]> up(new int[10]);
up.release(); // automatically uses delete[] to destroy its pointer
<div class="se-preview-section-delimiter"></div>
- unique_ptrs that point to arrays provide operations in Table 12.6(overleaf).
- When a unique_ptr points to an array, we cannot use the dot and arrow member access operators, we can use the subscript operator to access the elements in the array:
for(size_t i = 0; i != 10; ++i)
up[i] = i; // assign a new value to each of the elements
<div class="se-preview-section-delimiter"></div>
- shared_ptrs provide no direct support for managing a dynamic array. If we want to use a shared_ptr to manage a dynamic array, we must provide our own deleter.
shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });
sp.reset(); // uses the lambda we supplied that uses delete[] to free the array
<div class="se-preview-section-delimiter"></div>
- We pass a lambda(§10.3.2) that uses delete[] as the deleter. Had we neglected to supply a deleter, this code would be undefined.
- There is no subscript operator for shared_ptrs, and the smart pointer types do not support pointer arithmetic. To access the elements in the array, we must use get to obtain a built-in pointer, which we can then use in normal ways.
// shared_ptrs don't have subscript operator and don't support pointer arithmetic
for(size_t i = 0; i != 10; ++i)
*(sp.get() + i) = i; // use get to get a built-in pointer
<div class="se-preview-section-delimiter"></div>
Exercises Section 12.2.1
Exercise 12.23
Write a program to concatenate two string literals, putting the result in a dynamically allocated array of char. Write a program to concatenate two library strings that have the same value as the literals used in the first program.
<div class="se-preview-section-delimiter"></div>
#include <iostream>
<div class="se-preview-section-delimiter"></div>
#include <string>
<div class="se-preview-section-delimiter"></div>
#include <cstring>
using namespace std;
int main()
{
// Dynamically allocated array of char
char *result1 = new char[strlen("gaoxiang") + 1]();
strcat(result1, "gao");
strcat(result1, "xiang");
cout << result1 << '\n';
delete [] result1;
// string
string str1 = "gao", str2 = "xiang";
string result2 = str1 + str2;
cout << result2 << '\n';
}
<div class="se-preview-section-delimiter"></div>
Exercise 12.24
Write a program that reads a string from the standard input into a dynamically allocated character array. Describe how your program handles varying size inputs. Test your program by giving it a string of data that is longer than the array size you’ve allocated.
<div class="se-preview-section-delimiter"></div>
#include <iostream>
using namespace std;
int main()
{
int length = 0;
while(true)
{
cout << "Input your string length: ";
cin >> length;
char *str = new char[length + 1]();
cout << "Input string: ";
cin >> str;
cout << str << '\n';
delete[] str;
}
}
<div class="se-preview-section-delimiter"></div>
Exercise 12.25
Given the following new expression, how would you delete pa?
int *pa = new int[10];
delete [] pa;
12.2.2. The allocator Class
- An aspect of new that limits its flexibility is that new combines allocating memory with constructing object in that memory. Similarly, delete combines destruction with deallocation.
- In general, coupling allocation and construction can be wasteful. Decoupling construction from allocation means that we can allocate memory in large chunks and pay the overhead of constructing the objects only when we actually need to create them.
string *const p = new string[n]; // construct n empty strings string s;
string *q = p; // q points to the first string -
- This new allocates and initializes n strings. But we might not need n strings; a smaller number might suffice. So we may have created objects that are never used.
- For those objects we do use, when we assign new values over the previously initialized objects, they are written twice: first when the elements are default initialized, and second when we assign to them.
- Classes that do not have default constructors can’t be dynamically allocated as an array.
The allocator Class
- The library allocator class(defined in ) lets us separate allocation from construction. It provides type-aware allocation of raw, unconstructed, memory. Table 12.7 outlines the operations that allocator supports.
- To define an allocator we must specify the type of objects that a particular allocator can allocate. When an allocator object allocates memory, it allocates memory that is appropriately sized and aligned to hold objects of the given type.
allocator<string> alloc; // object that can allocate strings
auto const p = alloc.allocate(n); // allocate n unconstructed strings
<div class="se-preview-section-delimiter"></div>
allocators Allocate Unconstructed Memory
- The memory an allocator allocates is unconstructed. We use this memory by constructing objects in that memory.
- The construct member that takes a pointer and zero or more arguments constructs an element at the given location. The additional arguments that are used to initialize the object must be valid initializers for an object of the type being constructed. If the object is a class type, these arguments must match a constructor for that class.
auto q = p; // q will point to one past the last constructed element
alloc.construct(q++); // *q is the empty string
alloc.construct(q++, 10, '0'); // *q is 0000000000
alloc.construct(q++, "hi"); // *q is hi!
<div class="se-preview-section-delimiter"></div>
- It is an error(Segmentation fault) to use raw memory in which an object has not been constructed.
- When we’re finished using the objects, we must destroy the elements we constructed, which we do by calling destroy on each constructed element. The destroy function takes a pointer and runs the destructor on the pointed-to object.
while(q != p)
alloc.destroy(--q); // free the strings we actually allocated
<div class="se-preview-section-delimiter"></div>
- At the beginning of loop, q points one past the last constructed element. We decrement q before calling destroy. We destroy the first element in the last iteration, after which q will equal p and the loop ends.
- We may destroy only elements that are actually constructed. Once the elements have been destroyed, we can either reuse the memory to hold other strings or return the memory to the system. We free the memory by calling deallocate.
alloc.deallocate(p, n);
<div class="se-preview-section-delimiter"></div>
- The pointer we pass to deallocate must point to memory allocated by allocate. The size argument n must be the same size as used in the call to allocate that obtained the memory to which the pointer points.
Algorithms to Copy and Fill Uninitialized Memory
- The library defines two algorithms that can construct objects in uninitialized memory. These functions, described in Table 12.8, are defined in .
- Assume we have a vector of ints that we want to copy into dynamic memory. We’ll allocate memory for twice as many ints as are in the vector: construct the first half of the newly allocated memory by copying elements from the original vector; construct elements in the second half by filling them with a given value:
// allocate twice as many elements as vi holds
auto p = alloc.allocate(vi.size() * 2);
// construct elements starting at p as copies of elements in vi
auto q = uninitialized_copy(vi.begin(), vi.end(), p);
// initialize the remaining elements to 42
uninitialized_fill_n(q, vi.size(), 42);
<div class="se-preview-section-delimiter"></div>
- uninitialized_copy takes three iterators: the first two denote an input sequence and the third denotes the destination into which those elements will be copied. The destination iterator passed to uninitialized_copy must denote unconstructed memory.
- uninitialized_copy returns its(incremented) destination iterator. Thus, a call to uninitialized_copy returns a pointer positioned one element past the last constructed element. In this example, we store that pointer in q, which we pass to uninitialized_fill_n. This function takes a pointer to a destination, a count, and a value. It will construct the given number of objects from the given value at locations starting at the given destination.
Exercises Section 12.2.2
Exercise 12.26
Rewrite the program on page 481 using an allocator.
<div class="se-preview-section-delimiter"></div>
#include <iostream>
<div class="se-preview-section-delimiter"></div>
#include <string>
using namespace std;
void Fun(int n)
{
allocator<string> alloc;
auto const p = alloc.allocate(n);
string s;
auto q = p;
while (cin >> s && q != p + n)
{
alloc.construct(q++, s);
}
while (q != p)
{
cout << *--q << " ";
alloc.destroy(q);
}
alloc.deallocate(p, n);
}
int main()
{
Fun(3);
}
12.3. Using the Library: A Text-Query Program(Omit)
Please indicate the source: http://blog.csdn.net/gaoxiangnumber1
Welcome to my github: https://github.com/gaoxiangnumber1