18. Vectors and Arrays

"Caveat emptor!" -- Ancient advice

18.1 Introduction

We are looking at how to go from the hardware supported types "upward" to types that act the way users, not hardware, want.

In this chapter we focus on copying, and how it relates to initialization, cleanup, equality testing, etc.

18.2 Initialization

We usually should initialize our variables rather than accepting the default value. Initializer lists are in curly braces: {}. We can write:

vector v = {1, 2, 3];
                        

Or:

vector v = {1, 2, 3];
                        

18.3 Copying

Consider an implementation of vector like the following:

class vect {
    int sz;
    double* elem;
public:
    vect(int s)
        :sz{s}, elem{new double [s]}  { /*  */ }
    ~vect()
        { delete[] elem; }
    double get(int i) { return elem[i]; }
    void set(int i, double d) { elem[i] = d; }
};
                        

Then what happens in the following code?

void f(int n)
{
    vect v(3);
    v.set(2, 2.2);
    vect v2 = v;
    v.set(1, 9.9);
    v2.set(0, 8.8);
    cout << v.get(0) << ' ' << v2.get(1);
}
                        

Disaster!

18.3.1 Copy constructors

What to do? Create a copy constructor.

vector::vector(const vector& arg)
// allocate space, then initialize via copy
    :sz{arg.sz}, elem{new double[arg.sz]}
{
    copy(arg.elem, arg.elem+arg.sz, elem);  // from std lib
}
                            

18.3.2 Copy assignments

We get the same problem as above, plus a memory leak, on default assignments. Here's what we need to do:

vector& vector::operator=(const vector& a)
// make this vector a copy of a
{
    double* p = new double[a.sz];
    copy(a.elem, a.elem+a.sz, p);
    delete[] elem;
    elem = p;
    sz = a.sz;
    return *this;  // return self-ref
}
                            

18.3.3 Copy terminology

Let's look at shallow copies versus deep copies.

In a shallow copy both variables point to the same memory.

In a deep copy each variable points to different memory.

18.3.4 Moving

We can write a move assignment operator when we want to explicitly re-use the storage for one object in another. It looks like this:

vector& vector::operator=(vector&& a)
                            

The compiler will call this automatically when it sees an assigned element is going out of scope.

Drill

vec1.cpp:
Write a vector class with the default copy constructor and assignment operator. write a main with a function like f() above. Loop calling it and see what happens.

vec2.cpp:
Copy that code and add proper copy constructor and assignment operator. Run your loop again. Things should work now.

18.4 Essential operations

The C++ FDA recomments the following "7 essential operations" for a class:

  • Constructor with args
  • Default constructor
  • Copy constructor
  • Copy assignment
  • Move constructor
  • Move assignment
  • Destructor
18.4.1 Explicit constructors

The keyword explicit prevents default type conversions for objects when we don't want them. Good example:

class complex {
    public:
        complex(double); // construct a complex # from a double
};

complex z = 3.14;  // OK!
                            

Bad example:

vector v = 10;
v = 20;  // this would assign a new vector of 20 doubles to v!
                            

18.4.2 Debugging constructors and destructors

These are not called just where you see them! Rule:

  • Whenever an object of type X is created, one of X's constructors is invoked.
  • Whenever an object of type X is destroyed, X's destructor is invoked.
Drill

Do the exercise on pages 644-645 of the textbook.

18.5 Access to vector elements

We have used get() and set() methods to access elements of our vector so far. We can instead use the array access operator if we overload it as follows:

class vect {
    // .. our other code!
    double& operator[](int n) { return elem[n]; }
};
                        

18.5.1 Overloading on const

The above won't allow something like x = v[7] if v is a const in that context because it can't tell we aren't going to change v. We can write a second version that provides access to elements in a read-only fashion:

class vect {
    // .. our other code!
    double operator[](int n) const { return elem[n]; }
};
                        

Drill

Overload the [] operator for your vect class.

18.6 Arrays

Short version of section: you should use vector, not arrays! But we need to understand arrays to read old code, C code, and so on. Let's say we are having trouble with Python lists, and we need to look at the source code: we'd better understand arrays!

18.6.1 Pointers to array elements

We can point into the middle of an array:

double ad[10];
double* p = &ad[10];
                            

18.6.2 Pointers and arrays

An array can "turn into" a pointer:

char ch[100];
char* p = ch;
                            

sizeof(ch) is 100. But sizeof(p) is 4 (on 32-bit systems) or 8 (64-bit).

18.6.3 Array initialization

We can initialize character arrays with a string, like this:

char ac[] = "Beorn";
                            

This will create a six byte array:

Graphic drawn on whiteboard for now!
18.6.4 Pointer problems

A quick summary of some pointer problems:

  • Acces through the null pointer
  • Access through an unitialized pointer
  • Access off the end of an array
  • Access to a deallocated object
  • Access to an object that has gone out of scope
18.7 Examples: palindrome

18.7.1 Palindromes using string

18.7.2 Palindromes using arrays

18.7.3 Palindromes using pointers

Test Yourself!
  1. A good use of shallow copying would be when
    1. we have a very large object to copy
    2. we just need to scoop out the top few values from an object
    3. we don't really care if all the field values are the same
  2. A good use of deep copying would be when
    1. we need to make sure the values in one object change when values in the other do
    2. we need to make sure each objects uses separate memory
    3. we need to save memory
  3. We want a default constructor when
    1. we can establish meaningful invariants using default values
    2. we really don't know what should initialize a class
    3. we want to eliminate the faults (defaults) from our classes
  4. We need a destructor when our class
    1. has many members
    2. has default values
    3. has no copy constructor
    4. acquires resources
  5. If we write char prof = "Callahan"; that will create a string of ___ bytes:
    1. 12
    2. depends on the compiler
    3. 8
    4. 9
  6. Among common pointer problems are:
    1. access off the end of an array
    2. access through the null pointer
    3. all answers are correct
    4. access to a deallocated object
  7. In C++, we can give our own classes array-style access by:
    1. overloading the [] operator
    2. all answers are correct
    3. writing get() and set() functions
    4. using pointers to pointers
  8. If we write char ch[100] then sizeof(ch) will be:
    1. 8
    2. 4
    3. 200
    4. 100
  9. If we have a pointer in 32-bit Windows, its size most likely is
    1. the size of whatever it points to
    2. 8
    3. 4
Answers

1. a; 2. b; 3. a; 4. d; 5. d; 6. c; 7. a; 8. d; 9. c;

Drill