Programs manipulate data, which must be stored somewhere.
(This is different from scope, which is a compile-time attribute of identifiers.)
int i;
int *p;
int area = 500;
double side = sqrt(area);
double *ptr = &side;
Static variables that are not explicitly initialized are implicitly initialized to 0 converted to the type.
int i;
bool b;
double x;
char *p;
is equivalent to
int i = 0;
bool b = false;
double x = 0.0;
char *p = 0; // null pointer
Static storage is
int f(int start, int size) {
int total = 0;
int tmp;
for (int i = start; i < size; i++) { ... }
}
Local storage is
Class types:
Point *p; // uninitialized pointer
p = new Point; // default constructor
p = new Point(1,3);
cout << p->x << ' ' << p->y << '\n';
delete p;
and similarly for primitive types.
A pointer can also address a dynamically allocated array:
int *arr;
arr = new int[n];
for (int i = 0; i < n; i++)
arr[i] = f(i) + 3;
delete[] arr;
Note the special syntax for deletion syntax,
which is required because C++ doesn't distinguish a pointer to an int
from a pointer to an array of ints.
A class C may include a destructor ~C(), to release any resources (including storage) used by the object.
class C {
Date *today;
int *arr;
public:
C() { today = new Date(); arr = new int[50]; }
virtual ~C() { delete today; delete[] arr; }
};
Destructors are called in the opposite order to constructors.
Suppose B is a derived class of A. Then in
A *p;
p = new B;
...
delete p;
the destructor ~B() will not be called unless A's
destructor is virtual.
So why aren't destructors virtual by default? Because that would be a little less efficient.
| Storage allocated, initialized by a constructor | The destructor is called, storage reclaimed | |
| static object | when the program starts | when the program terminates |
|---|---|---|
| local object | when the declaration is executed | on exit from the function or block |
| free object | when new is called | when delete is called |
| subobject | when the containing object is created | when the containing object is destroyed |
class String {
int len;
char *chars;
public:
String(const char *s) :
len(strlen(s)), chars(new char[len]) {
for (int i = 0; i < len; i++)
chars[i] = s[i];
}
// more to come later ...
};
We also have a default constructor making an empty string:
class String {
int len;
char *chars;
public:
String() : len(0), chars(new char[0]) {}
// ...
};
The constructors allocate dynamic storage, so the destructor must delete it:
class String {
int len;
char *chars;
public:
// ...
virtual ~String() { delete[] chars; }
// ...
};
String foo = "bar";
invokes the above constructor.
String(const String &s);
Here are some initializations:
{
String empty;
String s1("blah blah");
String s2(s1); // initialized from s1
String s3 = s1; // initialized from s1
} // all four strings are destroyed here
We define a copy constructor to copy the character array:
String(const String &s) :
len(s.len),
chars(new char[len]) {
for (int i = 0; i < len; i++)
chars[i] = s.chars[i];
}
This copying (deep copy) is typical:
with explicit deallocation, it is generally unsafe to share.
In this case, Java is more efficient.
String & operator= (const String &s);
Consider
{
String s1("blah blah");
String s2("do be do");
s1 = s2; // assignment
} // the two strings are destroyed here
We define an assignment operator inside the String class:
String & operator= (const String &s) {
if (&s != this) { // don't copy onto self
delete[] chars;
len = s.len;
chars = new char[len];
for (int i = 0; i < len; i++)
chars[i] = s.chars[i];
}
return *this;
}
class ostream {
...
public:
ostream & operator<<(const char *s) {
for ( ; *s != '\0'; s++)
*this << *s;
return *this;
}
};
class String {
private:
String (const String &s) {}
String & operator= (const String &s) {
return *this;
}
...
The compiler will not generate them,
but the programmer will not be able to use these ones.
Any attempt to copy strings will result in a compile-time error.
For each class, the compiler will automatically generate the following member functions, unless the programmer supplies them:
If these defaults are not what we want, these functions must be defined.
XYZ(const XYZ &x);
This will typically copy any resources
that would be destroyed by the destructor.
XYZ &operator= (const XYZ &x) {
if (&x != this) {
// action of destructor
// action of copy constructor
}
return *this;
}
but may do something smarter (e.g. reusing instead of deleting).