Mixing and Matching: Using STL with the Base Class Library
If you're porting older C++ code to the .NET Framework, you probably already know that code using templates and the STL will compile to intermediate language without any difficulty and will run in the runtime as managed code. But, if you're writing a new Managed C++ application and you're thinking of using collections in that code, you may be unsure whether to use STL collections or a class from the Base Class Library such as System::Collections::ArrayList. In this column, I'll compare your choices.
The STL Vector Template
The world of the Standard Template Library can be very confusing, but vector provides a gentle introduction. Here's some code to build a growing array of integers and look at the value of the last one added:
#include <vector> using namespace std; // . . . vector
integers; integers.push_back(3); integers.push_back(5); int i = integers.back();
The neat thing about using vector is that it provides a type-safe collection. If I try to add a string to it, like this:
I get a compiler error:
error C2664: 'std::vector<int,class std::allocator<int> > ::push_back' : cannot convert parameter 1 from 'char ' to 'const int &' Reason: cannot convert from 'char ' to 'const int' This conversion requires a reinterpret_cast, a C-style cast or function-style cast
The message makes it clear that only integers can be added to this collection, and because it can't convert my character array to an integer, I get an error.
If I want to use this growing array of integers in a Managed C++ application, what changes? Nothing. I just have to remember to box the integers when I want to write them out with Console::Write or Console::WriteLine—but that's because those functions expected managed data, not fundamental types like integers. Here's how that works:
Console::Write("Last element is "); Console::WriteLine(__box(i));
A Vector of Managed Object Pointers
Well, that's straightforward enough, because it's working with a fundamental type (int) that isn't new to Managed C++. What if you wanted a collection of managed objects, or more accurately a collection of pointers to managed objects, such as System::String strings? A sensible first try is:
vector<System::String* > strings;
However, that doesn't work. The error message starts out like this:
error C2440: '=' : cannot convert from 'System::String __gc *__gc * ' to 'System::String __gc ** ' Cannot convert a managed type to an unmanaged type C:\Program Files\Microsoft Visual Studio .NET\Vc7\ include\vector(332) : while compiling class-template member function 'void std::vector<_Ty,_Ax>::push_back (System::String __gc *const __gc & )'
You might think the problem is a missing __gc and try this:
vector<System::String __gc* > strings;
But, in fact, you'll get the same error message.
A little Googling will reveal no shortage of people who are sure you can't use pointers to managed objects ( __gc* pointers) from old-fashioned unmanaged code that's expecting unmanaged pointers. But they're wrong. You just need to know how to convert between managed and unmanaged pointers to objects. I find it highly appropriate that the solution (provided by the Visual C++ Dev Team) is a template. It's called gcroot and it turns a pointer to a managed object into a harmless integer. When you use the integer, it maps back to a pointer into the managed heap and gives you access to your original managed object. Here's an example:
#include <vcclr.h> // . . . vector<gcroot<System::String*> > strings; strings.push_back(S"Hello"); strings.push_back(S"Vector "); String* s = strings.back(); Console::Write("Last element is "); Console::WriteLine(s);
There you go! An STL vector that holds pointers to System::String managed objects. Notice you don't need to do any casting or converting when you get your strings back out of the vector. Because it's a type-safe collection, the back() method returns a gcroot<String*>—and that template has a conversion operator to String*, of course.
Base Class Library Collections
People who believe you can't use managed objects in STL collections recommend you use the collection classes provided by the Base Class Library. Why not take a quick look at one of them so you can see how it compares to vector. ArrayList is a growing array, very similar to vector. Here's how to use it with integers:
Collections::ArrayList* ints = new Collections::ArrayList(); ints->Add(__box(3)); ints->Add(__box(5)); Object* o = ints->get_Item(1); // zero-based int i2 = System::Int16::Parse(o->ToString()); Console::Write("Last element is "); Console::WriteLine(__box(i2));
In a word: bleah. The problem is that ArrayList (and its siblings in the Collections namespace) are designed to work with pointers to managed objects, not with fundamental types, so you have to box the integers before you put them in there. And because they aren't template-based, the get_Item function (a C++-specific function that's half of the Item property) returns an Object* that you have to convert back to an integer. And what about type-safety? There isn't any. This line compiles just fine:
Is it just because you're using fundamental types? Here's an ArrayList that uses String pointers:
Collections::ArrayList* morestrings = new Collections::ArrayList(); morestrings->Add(S"Hello"); morestrings-<Add(S"Vector "); String* s2 = static_cast<String*> (morestrings->get_Item(1)); Console::Write("Last element is "); Console::WriteLine(s2);
Although you don't have to box the strings to add them to the collection, you do have to cast them on the way out. And you still don't have type-safety.
Which Collection to Use
I think the STL collections are your first choice. The syntax is neater and cleaner, you don't have to cast on the way out, and you get type-safety. As long as you know about the gcroot template (admittedly a fairly obscure thing), you can use STL collections for both managed and unmanaged objects.
However, if you are writing code that returns a collection or takes one as a parameter, then I recommend you use something from the System::Collections namespace. VB.NET or C# code that calls your methods won't know what to do if you hand it a vector of anything. Keep your public interface suitable for use from all languages, even the template-deprived ones.
About the Author
Kate Gregory is a founding partner of Gregory Consulting Limited (www.gregcons.com). In January 2002, she was appointed MSDN Regional Director for Toronto, Canada. Her experience with C++ stretches back to before Visual C++ existed. She is a well-known speaker and lecturer at colleges and Microsoft events on subjects such as .NET, Visual Studio, XML, UML, C++, Java, and the Internet. Kate and her colleagues at Gregory Consulting specialize in combining software develoment with Web site development to create active sites. They build quality custom and off-the-shelf software components for Web pages and other applications. Kate is the author of numerous books for Que, including Special Edition Using Visual C++ .NET.