Static memory is used for local static objects, for class static data members and for variables defined outside any func. Stack memory is used for nonstatic objects defined inside funcs. Objs allocated in static or stack memory are automatically created and destroyed by the compiler.
In addition to static or stack memory, programs can use heap memory to dynamically allocate objects. Dynamic memory is managed through a pair of operators:
To make using dynamic memory easier (and safer), the new library provides two smart pointer types that manage dynamic objs:
shared_ptr
.shared_ptr is a smart ptr that retains shared ownership of an obj through a ptr. Several shared_ptr
objs may own the same obj. The obj is destroyed and its mem deallocated when either of the following happens:
shared_ptr
owning the obj is destroyed;shared_ptr
owning the obj is assigned another ptr via operator=
or reset()
The obj is destroyed using delete-expression
or a custom deleter that is supplied to shared_ptr
during construction.
//a default inited smart ptr holds a null ptr
shared_ptr<string> p1; //point at a str
shared_ptr<list<int>> p2; //point at a list of ints
//smart ptr is used similarly to normal ptr
//if p1 is not null, check whether it's the empty str
if(p1 && p1->empty())
*p1 = "hi"; //if so, deref p1 to assign a new val to that str
make_shared
allocates and initializes an obj in dynamic memory, and returns a shared_ptr
that points to that obj. Like the sequential-container emplace
members, make_shared
uses its arguments to construct an obj of the given type.
//shared_ptr that points to an int with val 42
shared_ptr<int> p3 = make_shared<int>(42);
//p4 points to a string with value 99999
shared_ptr<string> p4 = make_shared<string>(5, '9');
//p5 points to an int that is value inited to 0
shared_ptr<int> p5 = make_shared<int>();
//p6 points to a dynamically allocated, empty vector<string>
auto p6 = make_shared<vector<string>>();
when we copy or assign a shared_ptr
, each shared_ptr
keeps track of how many other shared_ptr
s point to the same obj:
auto p = make_shared<int>(42); //obj to which p points has one user
auto q(p); //p and q points to the same obj
//obj to which p and q point has two users
shared_ptr
can be thought to have an associated counter, called as reference count. Whenever we copy a shared_ptr
, the count is incremented. E.g., the counter is incremented when we use it to init another shared_ptr
, when we use it as the right-hand operand of an assignment, or when we pass it to or return it from a func by value; the counter is 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.
Once a shared_ptr
's counter goes to zero, the shared_ptr
automatically frees the obj that it manges:
auto r = make_shared<int>(42); //int to which r points has one user
r = q; //assign to r, making it point to different addr
//increase the use count for the obj to which q points
//reduce the use count of the obj to which r had pointed
//the obj r had pointed to has no users; that obj is automatically freed
shared_ptr
s automatically destroy their objects, and automatically free the associated memory:
The dstr for shared_ptr
decrements the ref count of the obj to which that shared_ptr
points. If the count goes to zero, the shared_ptr
dstr destroys the obj to which the shared_ptr
points and frees the mem used by that obj (calling the obj's dstr).
void use_factory(T arg) {
shared_ptr<Foo> p = factory(arg);
//use p
} //p goes out scope; the mem to which p points is auto freed
shared_ptr<Foo> use_factory(T arg) {
shared_ptr<Foo> p = factory(arg);
//use p
return p; //ref count is incremented when we rtn p
} //p goes out of scope; the mem to which p points is not freed
Programs tend to use dynamic memory for one of three purposes:
vector<string> v1; //empty vector
{ //new scope
vector<string> v2 = {"a", "an", "the"};
v1 = v2; //copies the elems from v2 into v1
} //v2 is destroyed, which destroys the elems in v2
//v1 has three elems, which are copies of the ones orginally in v2
Blob<string> b1; //emoty Blob
{ //new scope
Blob<string> b2 = {"a", "an", "the"};
b1 = b2; //b1 and b2 share the same elems
} //b2 is destroyed, but the elems in b2 must not be destroyed
//b1 points to the elems originally created in b2
new
constructs an obj of type int on heap and returns a pointer to that object.
By default, dynamically allocated objects are default inited:
int *pi = new int; //pi points to dynamically allocated
//unamed, uninitialized int
string *ps = new string; //inited to empty string
int *pi = new int(1024); //obj to which pi points has value 1024
vector<int> *pv = new vector<int>{0,1,2,3,4};
//allocate and init a const int
const int *pci = new const int(1024);
//allocate a default-inited const empty string
const string *pcs = new const string;
free
destroys the obj to which its given ptr points, and it frees the crspding memory.
int i, *pi1 = &i, *pi2 = nullptr;
double *pd = new double(33), *pd2 = pd;
delete i; //ERROR: i is not a pointer
delete pi1; //undefined: pi1 refers to a local
//, which is a statically allocated obj
delete pd; //OK
delete pd2; //undefined: the mem pointed by pd2 was already freed
delete pi2; //OK: always ok to delete a null ptr
Compiler knows that i
is not a ptr. However, compilers cannot tell whether a ptr points to a statically or dynamically allocated obj; and, the compiler cannot tell whether mem addressed by a ptr has already been freed.
Although the value of a const
obj cannot be modified, the obj itself can be destroyed.
const int *pci = new const int(1024);
delete pci; //OK: deletes a const obj
Dynamic memory managed through built-in pointers (rather than smart pointers) exists until it is explicitly freed. After being deleted, the ptr becomes what is referred to as a dangling pointer. We can assign nullptr
to the ptr after using delete
.
However, resetting the value of a ptr after delete provides only limited protection:
int *p(new int(42)); //p points to dynamic mem
auto q = p; //p and q point to the same mem
delete p; //invalidates both p and q
p = nullptr; //indicates p is no longer bound to an obj
//but, resetting p has no effect on q
If we do not init a smart ptr, it is inited as a null ptr; we can also init a smart ptr from a ptr returned by new:
shared_ptr<double> p1; //shared ptr that can point to a double
shared_ptr<int> p2(new int(42)); //p2 points to an int with value 42
The smart ptr cstrs that take ptrs are explicit. Hence, we cannot implicitly convert a built-in ptr to a smart ptr; we must use the direct form of initialization to init a smart ptr:
shared_ptr<int> p1 = new int(1024); //ERROR: must use direct init
shared_ptr<int> p2(new int(1024)); //OK: uses direct init
shared_ptr<int> clone(int p) {
return new int(p); //ERROR: implicit conversion
}
shared_ptr<int> clone(int p) {
//OK: explicitly create from int*
return shared_ptr<int>(new int(p));
}
By default, a ptr used to init a smart ptr must point to dynamic memory because, by default, smart ptrs use delete to free the associated obj.
it is dangerous to use a built-in ptr to access an obj owned by a smart ptr; because we may not know when that obj is destroyed.
//ptr is created and inited when process is called
void process(shared_ptr<int> ptr) {
//use ptr
}//ptr goes out of scope and is destroyed
shared_ptr<int> p(new int(42)); //ref count is 1
process(p); //in process, ref count is 2
int i = *p; //OK: ref count is 1
int *x(new int(1024)); //danger: x is a plain ptr
process(x); //ERROR: cannot convert int* to shared_ptr<int>
process(shared_ptr<int>(x)); //legal, but the mem will be deleted
int j = *x; //undefined: x is a dangling ptr
get
returns a built-in ptr to the obj that the smart ptr is managing. The func is intended for cases when we need to pass a built-in ptr to code that can't use a smart ptr. The code that uses the return from get
must not delete that ptr.
shared_ptr<int> p(new int(42)); //ref count is 1
int *q = p.get(); //OK: but don't use q in any way that might delete its ptr
{ //new block
//undefined: two independent shared_ptrs point to the same mem
shared_ptr<int> (q);
} //block ends, q is destroyed, and the mem to which q points is freed
int foo = *p; //undefined: the mem to which p points was freed
Don't use get to initialize or assign another smart pointer.
reset
to assign a new ptr to a shared_ptr, by updating the ref count and, if appropriate, deletes the obj to which p points:
p.reset(new int(1024)); //OK: p points to a new obj
if(!p.unique())
p.reset(new string(*p)); //we aren't alone, allocate a new copy
*p += newVal; //now that we know we're the only ptr, ok to change this obj
when we use a smart ptr, the smart ptr class ensures that mem is freed when it is no longer needed even if the block is exited permaturely:
void f {
shared_ptr<int> sp(new int(42)); //allocate a new obj
//code that throws an exception that is not caught inside f
} //shared_ptr freed auto when the func ends
In contrast, mem that we manage directly is not auto freed when an exception occurs. If we use built-in ptrs to manage mem and an exception occurs after a new
but before the crspding delete
, then that mem won't be freed:
void f {
int *ip = nw int(42); //dynamically allocate a new obj
//code that throws an exception that is not caught inside f
delete ip; //free the mem before exiting
}
Classes that allocate resources - and that don't define dstrs to free those rscs - can be subject to the same kind of errors that arise when we use dynamic memory. It is easy to forget to release the rscs. Similarly, if an exception happens between when the rscs is allocated and when it is freed, the program will leak that rsc.
struct destination; //represents what we are connecting to
struct connection; //info needed to use the connection
connection connect(destination*); //open the connection
void disconnect(connection); //close the given conn
void f(destination &d /* other paras */){
//get a conn; must remember to close it when done
connection c = connect(&d);
//use the connection
//if we forget to call disconnect, there will be noway to close c
}
If connection
had a dstr, that dstr would auto close the connection when f
completes. However, no dstr is provided.
We can use shared_ptr
to ensure that the connection
is properly closed, by using our own deletion code.
void end_connection(connection *p) { disconnect(*p); }
void f(destination &d /* other paras */){
//get a conn; must remember to close it when done
connection c = connect(&d);
shared_ptr<connection> p(&c, end_connection);
//use the connection
//if we forget to call disconnect, there will be noway to close c
}
A unique_ptr
"owns" the obj to which it points. Unlike shared_ptr
, only one unique_ptr
at a time can point to a given obj. The obj to which a unique_ptr
points is destroyed when the unique_ptr
is destroyed.
unique_ptr<double> p1; //unique_ptr that can point at a double
unique_ptr<int> p2(new int(42)); //p2 points to int with value 42
Because a unique_ptr
owns the obj to which it points, it does not support ordinary copy or assignment:
unique_ptr<string> p1(new string("abcd"));
unique_ptr<string> p2(p1); //ERROR: no copy for unique_ptr
unique_ptr<string> p3;
p3 = p2; //ERROR: no assign for unique_ptr
Although we can't copy or assign 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) to p2
unique_ptr<string> p2(p1.release()); //release makes p1 null
unique_ptr<string> p3(new string("Trex"));
//transfers ownership from p3 to p2
p2.reset(p3.release()); //reset deletes the mem to which p2 had pointed
The release
returns the ptr currently stored in the unique_ptr
and makes that unique_ptr
null; thus, p2 is inited from the ptr value that had been stored in p1 and p1 becomes null. The reset
takes an optional ptr and repositions the unique_ptr
to point to the given ptr. If the unique_ptr
is not null, then the obj to which the unique_ptr
had pointed is deleted. The call to reset
on p2, therefore, frees the mem used by the string inited from "abcd", transfers p3's ptr to p2, and makes p3 null.
Calling release
breaks the connection bt a unique_ptr
and the obj it had been managing. The return is usually used to init or assign another smart ptr; if no smart ptr to hold the return, then the program takes over responsibility for freeing that rsc:
p2.release(); //WRONG: p2 won't free the mem and we've lost the ptr
auto p = p2.release(); //OK, but we must remeber to delete(p)
There's one exception to the rule that we cannot copy a unique_ptr
: we can copy or assign a unique_ptr that is about to be destroyed.
unique_ptr<int> clone(int p) {
//OK: explicitly create a unique_ptr<int> from int*
return unique_ptr<int>(new int(p));
}
unique_ptr<int> clone(int p) {
unique_ptr<int> ret(new int(p));
// ...
return ret;
}
In both cases, the compiler knows that the obj being returned is about to be destroyed, and hence, the compiler does a special kind of "copy".
overriding the deleter in a unique_ptr
affects the unique_ptr
type as well as we construct (or reset
) objs of that type.
//p points to an obj of type objT and uses an obj of type delT to free that obj
//it will call an obj named fcn of type delT
unique_ptr<objT, delT> p(new objT, fcn);
void f(destination &d /* other paras */){
connection c = connect(&d); //open the conn
//when p is destroyed, the conn 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 conn will be properly closed
}
A weak_ptr is a smart ptr that does not control the lifetime of the obj to which it points. Instead, a weak_ptr
points to an obj that is managed by a shared_ptr
. Binding a weak_ptr
to a shared_ptr
does not change the ref count of that shared_ptr
. Once the last shared_ptr
pointing to the obj goes away, the obj itself will be deleted. The obj will be deleted even if there are weak_ptr
pointing to it.
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); //wp weakly shares with p; use count in p is unchanged
Because the obj might no longer exist, we cannot use a weak_ptr
to access its obj directly. To access the obj, we must call lock
, which checks whether the obj to which the weak_ptr
points still exists. if so, lock
returns a shared_ptr
to the shared obj.
if(shared_ptr<int> np = wp.lock()) { //true if np is not null
//inside the if, np shares its obj with p
}
The language and library provide two ways to allocate an array of objects at once. The language defines a second kind of new
expr that allocates and initializes an array of objs; the library includs a template class named allocator
that lets us separate allocation from initialization.
new
allocates the requested number of objs and returns a ptr to the first one, instead getting an obj of array type:
//call get_size to determine how many ints to allocate
int *pia = new int[get_size()]; //pia points to the 1st of these ints
//can also allocate using a type alias
typedef int arrT[42]; //arrT names the type arr of 42 ints
int *p = new arrT; //allocates an arr of 42 ints
//compiler executes as if we had written int *p = new int[42];
initializing an array of dynamically allocated objects:
int *pia = new int[10]; //block of ten uninited ints
int *pia2 = new int[10]; //block of ten ints value inited to 0
string *psa = new string[10]; //block of ten empty strings
string *psa2 = new string[10](); //ditto
//block of ten ints each inited from the crspding initializer
int *pia3 = new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
//block of ten strings; first four are inited from the given initers
//remaining elements are value inited
string *psa3 = new string[10]{"a", "an", "the", string(3, 'x')};
freeing dynamic arrays
Elements in an array are destroyed in reverse order, i.e., from last to first.
delete p; //p must point to a dynamically allocated obj or be null
delete [] pa; //pa must point to dynamically allocated arr or be null
smart pointers and dynamic arrays
The library provides a version of unique_ptr
that can manage arrays allocated by new.
//up points to an arr of ten uninited ints
unique_ptr<int[]> up(new int[10]);
up.release; //auto uses delete[] to destroy its ptr
for(size_t i=0; i != 10; ++i)
up[i] = i; //assign a new value to each of the elems
unlike unique_ptr
, shared_ptr
s provide no direct support for managing a dynamic array, and we must provide our own deleter to manage:
//to use a shared_ptr we must supply deleter
shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });
sp.reset(); //uses the lambda to free the array
//shared_ptrs don't have subscript operator and don't support ptr arithmetic
for(size_t i=0; i != 10; ++i)
*(sp.get() + i) = i; //use get to get a built-in ptr
In general, coupling allocation and construction can be wasteful:
string *const p = new string[n]; //construct n empty strings
string s;
string *q = p; //q points to the first string
while(cin >> s && q != p+n)
*q++ = s; //assign a new value to *q
const size_t size = q - p; //remember how many strings we read
//use the array
delete[] p; //p points to an arr; must use delete[]
The new
expr allocates and inits n
strings, which might be larger than what we need. Moreover, for those objs we do use, we immediately assign new values over the previously inited strings. The elems that are used are written twice: first when the elems are default inited; and subsequently when we assign to them. More importantly, classes that do not have default cstrs cannot be dynamically allocated as array.
the library allocator class lets us separate allocation from construction. It provides type-aware allocation of raw, unconstructed, memory.
destroy
on any objs that were constructed in this mem before calling deallocate
;When an allocator
obj allocates memory, it allocates mem that is appropriately sized and aligned to hold objs of the given type:
allocator<string> alloc; //obj that can allocate strings
auto const p = alloc.allocate(n); //allocate n unconstructed strings
The mem an allocator allocates is unconstructed. We must construct
objects in order to use mem returned by allocate
:
auto q = p; //q will point to one past the last csted elem
alloc.construct(q++); //*q is the empty str
alloc.construct(q++, 10, 'c'); //*q is cccccccccc
alloc.construct(q++, "hi"); //*q is hi!
cout << *p << endl; //OK: uses the string output operator
cout << *q << endl; //disaster: q points to unconstructed mem
when we're finished using the objs, we must destroy the elems we constructed, by calling destroy
on each constructed elem:
while (q != p)
alloc.destroy(--q); //free the strings we actually allocated
once the elems have been destroyed, we can either reuse the mem to hold other strings or return the mem to the system. we free the mem by calling deallocate
:
alloc.deallocate(p, n);
algorithms to copy and fill uninitialized memory
As a companion to the allocator
class, the library also defines two algs that can construct objs in uninited mem.
Assume we have a vector
of int
s that we want to copy into dynamic mem. We'll allocate mem for twice as many int
s as are in the vector
. We'll construct the first half of the newly allocated mem by copying elems from the orig vector
, and construct elems in the second half by filling them with a given value:
//allocate twice as many elems as vi holds
auto p = alloc.allocate(vi.state() * 2);
//construct elems starting at p as copies of elems in vi
auto q = uninitialized_copy(vi.begin(), vi.end(), p);
//init the remaining elems to 42
uninitalized_fill_n(q, vi.size(), 42);