I wrote this article for the purpose of addressing common problems that are experienced by the programmers posting questions to the forums. I'll provide numerous examples along with detailed explanations of each.
Organization
Part 1) Passing arrays to functions
Part 2) Returning arrays from functions
First let me post a link to the arrays and templates tutorials of cplusplus.com. I think that it would be good for some to review these briefly before continuing. For the templates tutorial you only need to read the first section on function templates. Consider my article an amplification to the C++ tutorials on arrays and function templates.
http://www.cplusplus.com/doc/tutorial/arrays/
http://www.cplusplus.com/doc/tutorial/templates/
Terminology
The use of the word array will immediately result in some confusion for a number of reasons. First other languages have built in smart array types which do not work the same way as C/C++ arrays. The term array is defined in numerous dictionaries and so there is a generalized concept of an array which leads to confusion when discussing specific kinds of array types that are defined by C++ or some other language. The std::vector is described by the C++ standard as a sequence container but C++ programmers sometimes refer to the std::vector as a dynamic array. In fact any of the standard sequence containers that provide random access will fit into a more general definition of the term array. For instance, consider these definitions:
dictionary.com
Computers. a block of related data elements, each of which is usually identified by one or more subscripts.
Merriam Webster
(1) : a number of mathematical elements arranged in rows and columns (2) : a data structure in which similar elements of data are arranged in a table b : a series of statistical data arranged in classes in order of magnitude
When I use the term array I am talking about the more general definition of the concept that you would find in any dictionary. When referring to the "data structure" described by section 8.3.4 of the C++ std I will use the term C-Array. The following example shows an example of a C-Array. In it data is a C-Array. This type of a data structure existed in the C language and likewise exists within C++. However, I'll show you in numerous examples why it is sometimes better to consider using one of the standard sequence containers.
1 2 3
|
const int SIZE(5);
int data[SIZE];
std::generate(data, data + SIZE, rand);
| |
PART I - Passing arrays to functions
Compile and execute the program. It contains a defect which will be evident when you analyze the output.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
#include <iostream>
void printArray(int data[])
{
for(int i = 0, length = sizeof(data); i < length; ++i)
{
std::cout << data[i] << ' ';
}
std::cout << std::endl;
}
int main()
{
int data[] = { 5, 7, 8, 9, 1, 2 };
printArray(data);
return 0;
}
| |
You will see that only the first 4 elements of the array are printed. The sizeof(data) returns a value of 4! That happens to be the size of the pointer used to pass the array to printArray. That has a couple of implications. First the array does not get copied. The pointer to the first element of the array is copied. C-Arrays do not have copy constructors, assignment operators, or functional interfaces. In the following examples you will see examples using std::vector, std::deque, and std::list which are dynamic sequence containers provided by the C++ std template library. This is not a full tutorial on those containers but they are used in order to show the flexibility in the proposed improvements to the flawed program.
Let's look at another example. In it, I have created numerous printArray functions that are overloaded so that I can show a number of solutions. I will then analyze each of those solutions and explain the pros and cons of each.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
|
#include <iostream>
#include <vector>
#include <deque>
#include <list>
// Method 1: works but very little security. It is impossible to validate
// the inputs since the size of data still cannot be validated. If length is too large
// undefined behavior will occur.
void printArray(int data[], int length)
{
for(int i(0); i < length; ++i)
{
std::cout << data[i] << ' ';
}
std::cout << std::endl;
}
// Method 2: Type safe and more generic. Works with any container that supports forward iterators.
// Limitation - cannot validate iterators so caller could pass null or invalid pointers. Typesafe - won't
// allow you to pass inconsistent iterator types. Allows you to pass any valid range of a container.
template <class ForwardIteratorType>
void printArray(ForwardIteratorType begin, ForwardIteratorType end)
{
while(begin != end)
{
std::cout << *begin << ' ';
++begin;
}
std::cout << std::endl;
}
// Method 3 - This implementation is as typesafe and secure as you can get but
// does not allow a subrange since the entire container is expected. It could
// be useful if you want that extra security and know that you want to operate
// on the entire container.
template <class ContainerType>
void printArray(const ContainerType& container)
{
ContainerType::const_iterator current(container.begin()), end(container.end());
for( ;
current != end;
++current)
{
std::cout << *current << ' ';
}
std::cout << std::endl;
}
int main()
{
// Method 1.
const int LENGTH(6);
int data[LENGTH] = { 5, 7, 8, 9, 1, 2 };
printArray(data, LENGTH);
// Method 2.
printArray(data, data + LENGTH);
std::vector<int> vData(data, data + LENGTH);
printArray(vData.begin(), vData.end());
std::list<int> lData(data, data + LENGTH);
printArray(lData.begin(), lData.end());
std::deque<int> dData(data, data + LENGTH);
printArray(dData.begin(), dData.end());
// won't compile if caller accidentally mixes iterator types.
//printArray(dData.begin(), vData.end());
// method 3.
printArray(vData);
printArray(dData);
printArray(lData);
return 0;
}
| |
Method 2 is unique in that it allows you to specify any range of the array where method 1 and 2 accomplish the same goal of printing the entire container. If that is what your intention was all along then I submit to you that method 3 is the best. It is most secure and typesafe. There is very little if any chance that a caller could specify invalid parameters. An empty container would not cause any problem. The function simply wouldn't print any values.
It is important to note that a C-Array cannot be passed using method 3. Method 3 requires the use of a container such as the std::vector. C-Arrays are a holdover from the C language and do not have functional interfaces. Method 1 or 2 would need to be used if you are dealing with C-Arrays. I'm sure that there are other ways as well but it is up to you to determine which method is best for your project.
One could produce hundreds of example programs that demonstrate these points even further but I'll leave it up to the readers to copy the program and build other kinds of examples. The beauty of templates is that it reduces repetitive programming tasks. Define the function once so that the function can be called multiple times where each time you specify a different type. It is simply a matter of making sure that the type supports the minimim requirements of the function. The method 3 printArray function requires that the ContainerType has begin() and end() member functions that return forward iterators and that the objects within the container are instances of classes that support the operator<< function. The operator<< can be defined for user defined types as well so method 3 is not limited to containers of built in types.