Because arrays are evil.
Let's assume the best case scenario: you're an experienced C programmer, which almost by definition means you're pretty good at working with arrays. You know you can handle the complexity; you've done it for years. And you're smart the smartest on the team the smartest in the whole company. But even given all that, please read this entire FAQ and think very carefully about it before you go into "business as usual" mode.
Fundamentally it boils down to this simple fact: C++ is not C. That means (this might be painful for you!!) you'll need to set aside some of your hard earned wisdom from your vast experience in C. The two languages simply are different. The "best" way to do something in C is not always the same as the "best" way to do it in C++. If you really want to program in C, please do yourself a favor and program in C. But if you want to be really good at C++, then learn the C++ ways of doing things. You may be a C guru, but if you're just learning C++, you're just learning C++ you're a newbie. (Ouch; I know that had to hurt. Sorry.)
Here's what you need to realize about containers vs. arrays:
Here are some specific problems with arrays:
Here are some things to think about when using containers:
To net this out, arrays really are evil. You may not think so if you're new to C++. But after you write a big pile of code that uses arrays (especially if you make your code leak-proof and exception-safe), you'll learn the hard way. Or you'll learn the easy way by believing those who've already done things like that. The choice is yours.
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
Use the standard class template std::map<Key,Val>:
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
You can't, but you can fake it pretty well. In C/C++ all arrays are homogeneous (i.e., the elements are all the same type). However, with an extra layer of indirection you can give the appearance of a heterogeneous container (a heterogeneous container is a container where the contained objects are of different types).
There are two cases with heterogeneous containers.
The first case occurs when all objects you want to store in a container are publicly derived from a common base class. You can then declare/define your container to hold pointers to the base class. You indirectly store a derived class object in a container by storing the object's address as an element in the container. You can then access objects in the container indirectly through the pointers (enjoying polymorphic behavior). If you need to know the exact type of the object in the container you can use dynamic_cast<> or typeid(). You'll probably need the Virtual Constructor Idiom to copy a container of disparate object types. The downside of this approach is that it makes memory management a little more problematic (who "owns" the pointed-to objects? if you delete these pointed-to objects when you destroy the container, how can you guarantee that no one else has a copy of one of these pointers? if you don't delete these pointed-to objects when you destroy the container, how can you be sure that someone else will eventually do the deleteing?). It also makes copying the container more complex (may actually break the container's copying functions since you don't want to copy the pointers, at least not when the container "owns" the pointed-to objects).
The second case occurs when the object types are disjoint they do not share a common base class. The approach here is to use a handle class. The container is a container of handle objects (by value or by pointer, your choice; by value is easier). Each handle object knows how to "hold on to" (i.e. ,maintain a pointer to) one of the objects you want to put in the container. You can use either a single handle class with several different types of pointers as instance data, or a hierarchy of handle classes that shadow the various types you wish to contain (requires the container be of handle base class pointers). The downside of this approach is that it opens up the handle class(es) to maintenance every time you change the set of types that can be contained. The benefit is that you can use the handle class(es) to encapsulate most of the ugliness of memory management and object lifetime. Thus using handle objects may be beneficial even in the first case.
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
The most important thing to remember is this: don't roll your own from scratch unless there is a compelling reason to do so. In other words, instead of creating your own list or hashtable, use one of the standard class templates such as std::vector<T> or std::list<T> or whatever.
Assuming you have a compelling reason to build your own container, here's how to handle inserting (or accessing, changing, etc.) the elements.
To make the discussion concrete, I'll discuss how to insert an element into a linked list. This example is just complex enough that it generalizes pretty well to things like vectors, hash tables, binary trees, etc.
A linked list makes it easy insert an element before the first or after the last element of the list, but limiting ourselves to these would produce a library that is too weak (a weak library is almost worse than no library). This answer will be a lot to swallow for novice C++'ers, so I'll give a couple of options. The first option is easiest; the second and third are better.
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
A template is a cookie-cutter that specifies how to cut cookies that all look pretty much the same (although the cookies can be made of various kinds of dough, they'll all have the same basic shape). In the same way, a class template is a cookie cutter for a description of how to build a family of classes that all look basically the same, and a function template describes how to build a family of similar looking functions.
Class templates are often used to build type safe containers (although this only scratches the surface for how they can be used).
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
Consider a container class Array that acts like an array of integers:
Repeating the above over and over for Array of float, of char, of std::string, of Array-of-std::string, etc, will become tedious.
Unlike template functions, template classes (instantiations of class templates) need to be explicit about the parameters over which they are instantiating:
Note the space between the two >'s in the last example. Without this space, the compiler would see a >> (right-shift) token instead of two >'s.
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
Consider this function that swaps its two integer arguments:
If we also had to swap floats, longs, Strings, Sets, and FileSystems, we'd get pretty tired of coding lines that look almost identical except for the type. Mindless repetition is an ideal job for a computer, hence a function template:
Every time we used swap() with a given pair of types, the compiler will go to the above definition and will create yet another "template function" as an instantiation of the above. E.g.,
Note: A "template function" is the instantiation of a "function template".
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
When you call a function template, the compiler tries to deduce the template type. Most of the time it can do that successfully, but every once in a while you may want to help the compiler deduce the right type either because it cannot deduce the type at all, or perhaps because it would deduce the wrong type.
For example, you might be calling a function template that doesn't have any parameters of its template argument types, or you might want to force the compiler to do certain promotions on the arguments before selecting the correct function template. In these cases you'll need to explicitly tell the compiler which instantiation of the function template should be called.
Here is a sample function template where the template parameter T does not appear in the function's parameter list. In this case the compiler cannot deduce the template parameter types when the function is called.
To call this function with T being an int or a std::string, you could say:
Here is another function whose template parameters appear in the function's list of formal parameters (that is, the compiler can deduce the template type from the actual arguments):
Now if you want to force the actual arguments to be promoted before the compiler deduces the template type, you can use the above technique. E.g., if you simply called g(42) you would get g<int>(42), but if you wanted to pass 42 to g<long>(), you could say this: g<long>(42). (Of course you could also promote the parameter explicitly, such as either g(long(42)) or even g(42L), but that ruins the example.)
Similarly if you said g("xyz") you'd end up calling g<char*>(char*), but if you wanted to call the std::string version of g<>() you could say g<std::string>("xyz"). (Again you could also promote the argument, such as g(std::string("xyz")), but that's another story.)
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
Another way to say, "class templates."
A parameterized type is a type that is parameterized over another type or some value. List<int> is a type (List) parameterized over another type (int).
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
Yet another way to say, "class templates."
Not to be confused with "generality" (which just means avoiding solutions which are overly specific), "genericity" means class templates.
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
E-mail the author
[ C++ FAQ Lite
| Table of contents
| Subject index
| About the author
| ©
| Download your own copy ]
Revised Aug 15, 2001