CS 202 Fall 2013 > Notes for Thursday, September 26, 2013 |
CS 202 Fall 2013
Notes for Thursday, September 26, 2013
Dynamic Allocation [9.8]
Overview of Memory Allocation
Memory Allocation means obtaining some memory for our program to use. When we are done with it, the memory must be deallocated—returned to the system. In C++ programs, memory allocation can be done in three ways.
- Automatic Allocation
- This is the way normal local variables are handled.
Roughly speaking,
the memory is allocated when the opening brace
“
{
” is encountered and deallocated at the closing brace “}
”. - Static Allocation
- This is the way global variables, static local variables, and static member variables are handled. Memory is allocated when a program is loaded (before it executes). It is deallocated when the program ends.
- Dynamic Allocation
- Here our program has control over
when memory is allocated and deallocated.
A
new
statement requests memory; adelete
statement deallocates it.
Like automatic and static allocation, dynamic allocation does not give us raw memory; we specify a type, and the memory is initialized by calling a constructor. When we are done with it, the destructor is called.
Unlike automatic and static allocation, the values we obtain using dynamic allocation have no name. Instead of placing our value in some named location, we get a pointer to the value, which we must store somewhere if we are to use the allocated memory.
In C++,
dynamic allocation is the primary way to
obtain an arbitrarily sized block of memory
while a program is running.
If someone says, “But vector
does that,”
then I reply,
“Yes! But how does vector
do it?”
The answer is that the code that implements vector
does dynamic allocation,
using new
and delete
.
As knowledgeable programmers, we should know
not only how to use vector
,
but also how to write it.
In addition, we will need dynamic allocation in order to do some aspects of object-oriented programming.
Non-Array Form
To dynamically allocate a value of type Foo
,
we do
[C++]
new Foo
This allocates enough memory for a value of type Foo
,
calls the Foo
default constructor,
and returns a pointer to the value.
We need to store this pointer.
When we are done, we do a[C++]
Foo * fp = new Foo;
delete
on the pointer.
[C++]
delete fp;
This calls the Foo
destructor
and deallocates the memory
that the pointer points to.
It does not do anything to the pointer.
So when the delete
is done,
the pointer still points to the same location in memory,
but this is no longer a location we are allowed to use.
Do not use it!
It is very important that each dynamically allocated object be deallocated.
For every
new
there should be exactly onedelete
!
Between the new
and the delete
we can use the value.
[C++]
int * ip = new int; *ip = 3; *ip += 5; cout < < (*ip) << endl; // Should print 8 delete ip;
Array Form
We can also use new
to allocate an
array of multiple values.
We do this by putting brackets after the type.
The values obtained in this way are
always default-constructed.
[C++]
Foo * fa = new Foo[k];
If we allocated using the array form,
then we also need to deallocate using the array form:
put a pair of empty brackets “[]
”
just after the delete
.
[C++]
delete [] fa;
Between the new
and the delete
we can treat the pointer as if it is an array
(roughly, a vector
without the member functions).
[C++]
int * ia = new int[7]; for (int c = 0; c <7; ++c) { ia[c] = c*c; } for (int c = 0; c < 7; ++c) { cout << c[i] << endl; } delete [] ia;
RAII
Dynamic allocation without the corresponding deallocation
has been a thorn in the side of C and C++ programmers for decades.
The vector
class handles this nicely;
I still recommend it.
But dynamic allocation does have its place,
as we will see.
When you use it, one way to make sure that the
delete
happens
is to have the dynamic values managed by an object.
Perhaps the new
can be done in the
object’s constructor
(or perhaps not).
Then the destructor can do the delete
.
If the managing object is automatic or static,
then the destructor call is guaranteed,
and so the delete
is guaranteed to happen as well.
This is a common C++-programming idiom. It is universally referred to as “Resource Acquisition Is Initialization”—an unfortunate name, I think. But the abbreviation “RAII” is not so bad.
Summary
Non-Array | Array | |
---|---|---|
Allocate | Foo * fp = new Foo;
OR Foo * fp = new Foo(1, "abc"); |
Foo * fa = new Foo[k]; |
Use | *fp = x;
cout << *fp << endl;
fp->bar(); |
fa[i] = x;
cout << fa[i] << endl;
fa[i].bar(); |
Deallocate
In a destructor is a
good place for this. |
delete fp; |
delete [] fa; |
Classes II: The Copy Constructor [14.4]
When an object is passed by value to a function, a copy is made. The constructor called in such a situation is the copy constructor.
[C++]
void bar(Foo f) { ... private: int _a; string _b; }; Foo x; bar(x); // f is a copy of x, constructed with the Foo copy ctor
A class’s copy constructor is just a constructor
that takes a single parameter whose type is the class.
We cannot pass this by value (think about it).
The standard way to do this is to pass by
reference-to-const.
This is just like passing by reference,
except that we put a “const
”
before the parameter type;
this ensures that the value cannot be modified.
[C++]
class Foo { public: // Copy ctor Foo (const Foo & other) { ... } ...
We normally do not need to write a copy constructor. If we do not declare it, then the compiler will write one for us. The compiler-generated copy constructor will construct all data members using their copy constructors, passing as parameters the corresponding data members of the other object.
When do we write a copy constructor?
A good rule of thumb is to write it when you write a destructor.
So, if an object manages some dynamically allocated memory,
and you need to give the class a destructor so that it can
do a delete
,
then you probably need to write a copy constructor, too.
See
hasarray.h
and
hasarray.cpp
for the code for class HasArray
,
which, among other things,
does dynamic allocation
and has a copy constructor.
See
hasarray_main.cpp
for a C++ program that uses class HasArray
.
For today’s lab work, see the 9/26 Challenge.