Two ways to create an object

We have already seen that C++ offers two different ways to make an array.

In similar fashion, C++ offers two different ways to make and work with a vector.

The simplest method looks like this.

std::vector<int> v;
int x;

while(std::cin >> x)
  v.push_back(x);

for(int n = 0;n < v.size();n++)
  std::cout << v[n] << std::endl;

An alternative method uses a pointer to access the vector.

std::vector<int> *w;
int x;

w = new std::vector<int>();

while(std::cin >> x)
  (*w).push_back(x);

for(int n = 0;n < (*w).size();n++)
  std::cout << (*w)[n] << std::endl;

delete w;

In this second example w is not a vector. Instead, w is a pointer to a vector. The vector itself gets created in the statement

w = new std::vector<int>();

The new operator creates a new, empty vector of ints and returns a pointer to that vector. We assign that pointer to the pointer variable w, and use w for the remainder of the code to access the vector we created with new.

One complication of working with a pointer is that any time we want to access the vector that w points to we will have to dereference the pointer with *. That explains why you see syntax like (*w).size() and (*w)[n] in the example above.

A final difference you see here is that since we created the vector with new, we are also obligated to free the memory associated with the vector with delete when we are done working with the vector.

Alternative pointer syntax

Pointers are used quite often to access objects in C++. One annoyance that goes along with working with a pointer is the need to constantly dereference the pointer to work with the object that the pointer points to. In the example above, if we want to ask the vector that w points to what its size is we have to say

(*w).size();

The *w part of the expression dereferences the pointer, giving us access to the vector itself. Once we have access to the vector, we can call the size() method associated with that vector object.

This sort of situation occurs frequently in C++ programming, so the language offers a more convenient alternative syntax:

w->size();

You can use this syntax to access any method associated with an object that you are reaching via a pointer.

Unfortunately, this alternative syntax does not help with everything. We still have to use a dereference to do (*w)[n].

Passing objects as parameters

Just as you have a choice in how to create and access an object, you also have a choice in deciding how to pass an object as a parameter to a function.

One option is to pass the object by value.

template <typename T>
void printVector(std::vector<T> v)
{
  for(int n = 0;n < v.size();n++)
    std::cout << v[n] << endl;
}

This option uses perfectly simple and straight-forward syntax. In this version we pass the vector to the function in a simple and straight-forward way, and use it in a perfectly straight-forward way inside the function.

It turns out that there are two problems with this approach. The first problem is that passing an object by value almost always triggers a copy. In this case, any vector we pass to this function will get copied into the parameter v. The code in the body of the function will then work with that copy and not touch the original. If the vector in question is quite large, this extra copying step can introduce unwanted inefficiency.

The second problem is that sometimes when we pass an object to a function we truly want to work with the original object and not the copy. Consider this example.

template <typename T>
void readVector(std::ifstream in,std::vector<T> v)
{
  T x;
  while(in >> x)
    v.push_back(x);
}

Suppose we try to use this function in a program.

std::vector<int> A;
std::ifstream in;
in.open("numbers.txt");
readVector(in,A);
in.close();

std::cout << "A has " << A.size() << " items." << std::endl;

Running this snippet of code will result in the output

A has 0 items.

no matter how many numbers are in the file numbers.txt. What happened here is that passing A by value to the readVector function causes a copy of A to be made, and readVector works with that copy and not the original. readVector will then go on to read a bunch of data from the file and stuff those numbers onto the copy vector. When we exit the readVector function, the copy vector v (along with all of its data) will automatically vanish. The original vector A will be untouched throughout, and will end up just as empty as it started.

Given these two rather significant problems, it looks like we will need an alternative. The appropriate alternative is to once again fall back on using pointers and use a pointer parameter.

template <typename T>
void readVector(std::ifstream in,std::vector<T> *v)
{
  T x;
  while(in >> x)
    v->push_back(x);
}

In this version, we pass a pointer to a vector instead of the vector itself. Inside the function we use the pointer to access the vector and put things in the vector.

We can use this version of readVector in combination with the following code.

std::vector<int> *A;
A = new std::vector<int>();

std::ifstream in;
in.open("numbers.txt");
readVector(in,A);
in.close();

std::cout << "A has " << A->size() << " items." << std::endl;
delete A;

In this version there is only one vector - the vector we created with new. We pass a pointer to that vector to the readVector function, and readVector uses the pointer to put data in the vector. After doing this, the vector will report having data in it after we return from the call to readVector.

You can even use a pointer parameter with objects that you would not normally access via a pointer. You can get location information about the object by using the "address of" operator, &:

std::vector<int> A;
std::ifstream in;
in.open("numbers.txt");
readVector(in,&A); // Get the address of A and pass it to readVector
in.close();

std::cout << "A has " << A.size() << " items." << std::endl;

Pass by reference

There is a third way to pass an object as a parameter to a function. This alternative uses a third type of parameter, a reference parameter. To indicate that a parameter is a reference parameter we attach an & to the parameter.

template <typename T>
void readVector(std::ifstream &in,std::vector<T> &v)
{
  T x;
  while(in >> x)
    v.push_back(x);
}

A reference parameter is a hybrid of a value parameter and a pointer parameter. Reference parameters use the ordinary pass by value syntax, but behind the scenes a reference parameter is secretly implemented via a pointer. This means that reference parameters offer the convenience of conventional parameters coupled with the performance benefits of using a pointer.

Here are two examples demonstrating the use of this version of readVector. In both cases, the vector in question will report having data in it after we return from readVector.

// First example
std::vector<int> A;
std::ifstream in;
in.open("numbers.txt");
readVector(in,A);
in.close();

std::cout << "A has " << A.size() << " items." << std::endl;

// Second example
std::vector<int> *A;
A = new std::vector<int>();

std::ifstream in;
in.open("numbers.txt");
readVector(in,*A);
in.close();

std::cout << "A has " << A->size() << " items." << std::endl;
delete A;

Notice that in the second example we have to dereference the A pointer before passing the vector to the readVector function.