Containers

Okay, you've had it pretty easy until now, but it's about to get harder. C++ containers are a powerful but kind of funky feature, and it's important to understand how they work. Ready to learn something new?

There are three main types of containers you need to learn about. These are vectors, lists, an maps. All three of these are used extensively in Leadwerks, they act differently and it's important to use the right tool for the job.

Vectors


Not to be confused with the Vec3 class, which is a three-dimensional vector, C++ vectors are a resizable array, declared as follows:
std::vector myvector;

Where DATATYPE can be any kind of variable. Here is a vector of integer values:
std::vector myvector;

This code will create a vector of strings:
std::vector myvector;

When we declare a vector it will be empty, so there is no size to declare. We can resize a vector with the resize() method:
std::vector myvector;
myvector.resize(3);
myvector[0] = "Bob";
myvector[1] = "Jane";
myvector[2] = "Fred";

We can increase the vector size by one element and add a value to the end with the push_back method:
std::vector myvector;
myvector.push_back("Bob");

In C++ we can also size and fill the vector all at once just like an array:
std::vector myvector = {"Bob", "Jane", "Fred"};

Iterating Through Vectors


We can iterate through the vector in a manner similar to arrays:
std::vector myvector = {"Bob", "Jane", "Fred"};
for (int n=0; n{
Print(myvector[n]);
}

Iterators


There is another way we can loop through containers, using an iterator. We must declare the iterator type specifically to match our container. For example, an iterator to walk through a string vector is declared like this:
std::vector::iterator it;

We can then use a for loop to walk through all values in the vector, starting at the iterator returned by the begin() method and continuining until out iterator equals the iterator returned by the vector end() method. Each element is gotten with a pointer to the iterator:
std::vector myvector = {"Bob", "Jane", "Fred"};
std::vector::iterator it;
for (it = myvector.begin(); it != myvector.end(); it++)
{
std::string element = (*it);
Print(element);
}

Okay, that was pretty confusing. Remember the auto key word? Let's use that so we don't have to worry about typing out the iterator type:
std::vector myvector = {"Bob", "Jane", "Fred"};
for (auto it = myvector.begin(); it != myvector.end(); it++)
{
std::string element = (*it);
Print(element);
}

That's a little simpler. Iterators are one of those things in C++ you just need to copy and paste until you remember it.

Fortunately, iterators work pretty much the same way for vectors, lists, and maps. Once you learn this idea, you're in the big leagues!

Lists


C++ linked lists are a way to store all elements in a container that allows fast insertion and removal. Imagine if you wanted to remove the first element of a vector. You would have to copy the entire vector from position one to the end, then resize the vector to be one element smaller:
std::vector myvector = {"Bob", "Jane", "Fred"};
myvector[0] = myvector[1];
myvector[1] = myvector[2];
myvector.resize(2);

If we used this method on a very big vector it would be very slow. We could make it faster by copying the entire contents of the vector at once with memcpy but it's still very inefficient. Lists, on the other hand, can easily remove an element and it doesn't matter where it is located.

We declare a list as follows:

std::list mylist;

And use the push_front() method to insert an element at the beginning of the list. Notice we start with the last name and go backwards, since we are adding these at the front instead of the back:

mylist.push_front("Fred");
mylist.push_front("Jane");
mylist.push_front("Bob");

Removing Elements


The major advantage of lists is fast insertion and removal of objects in any order. If you need to store a bunch of objects that are constantly being created and destroyed, lists are the way to go.

There are two ways to remove items from a list, but only one of them will actually let us take advantage of fast removal of elements.

The Easy Way


There is a simple way to remove an element from a list with the remove() method:
mylist.push_front("Fred");
mylist.push_front("Jane");
mylist.push_front("Bob");
mylist.remove(mylist.begin(), mylist.end(), "Bob");

Unfortunately, this method is very slow when your list gets large. The entire list has to be iterated through to find all instances of the value you want to remove. Imagine if you had 1000 items in a list and you called remove() to remove 10 of them. Your program would have to check approximately 10,000 values! Therefore, take care not to use this method if you expect a list to contain large numbers of objects (more than 100), or if you are frequently adding and removing items. For faster removal of items from large lists we have another method that is much faster but a little trickier to use.

The Right Way


After we add an element, we can retrieve an iterator for that element with the begin() method:
std::list::iterator it = mylist.begin();

Let's use the auto key word to simplify that:
auto it = mylist.begin();

We can then use that iterator to remove the element from the list at any time:
mylist.erase(it);

Here it is all together, with a loop that prints out each element in the list:
std::list[[std::string]] mylist;

mylist.push_front("Fred");
mylist.push_front("Jane");
mylist.push_front("Bob");

auto it = mylist.begin();
mylist.erase(it);

for (it = mylist.begin(); it != mylist.end(); it++)
{
std::string element = (*it);
Print(element);
}


One thing you have to watch out for is erasing an iterator twice. There is no way to set an iterator value to "non-valid" so you have to keep careful track of whether you removed an element or not. Also, when you first declare an iterator there is no way to set its value to "invalid" so you have to carefully keep track of it. This is one of the major places you can mess up in C++, so be careful!

Maps


Not to be confused with Leadwerks map files, the C++ std::map is an associative array with a key and a value for that key. You must declare the data type for both the key and the map:
std::map mymap

You can start inserting values into a map right away:

mymap["health"] = 100;
Print(mymap["health"]);

It is unlikely you will need to iterate through all values in a map, but if you do it works similarly to lists:

mymap["health"] = 100;
mymap["ammo"] = 60;

for (std::map::iterator it = mymap.begin(); it != mymap.end(); it++)
{
std::string key = (*it).first;
int value = (*it).second;
Print(key + " = " + value);
}


That iterator declaration is ridiculously long, so let's simplify it with auto:
for (auto it = mymap.begin(); it != mymap.end(); it++)

Removing Elements


The simplest way to remove a value from a map is to just set it to NULL, zero, or "":
mymap["health"] = 0;

Note that the key pair still exists in the map, and the value has just been set to NULL or zero. This method will actually remove the item:
mymap.erase("health");

If you want to see if a map even contains a value for a specific key, you can use the find method:
auto it = mymap.find("health");

The find method will always return a valid iterator, whether or not the map contains the key. Therefore, we will check to see if it equals the iterator returned by the end() method:
if (it!=mymap.end())
{
int health = mymap["health"];
}

This is the safe way to remove map elements.

Conclusion


There are other container types in C++ like multi-maps and hash tables, but these aren't as commonly used as the three container types described here. If you wish to, you can read about more container types here. Containers are one of the hardest concepts in C++ so if you made it this far you're doing great.