Boxing Value Types in Managed C++

From Kate Gregory's Codeguru column, "Using Visual C++ .NET".

Sometimes things that should be really simple don't feel simple at all when you try to do them. Take, for example, writing the value of a variable to the screen. You know how to do it in "classic" C++, say for example in Visual C++ 6:

int x = 3;
cout << "x is " << x << endl;

No problem. Whatever "Introduction to C++" you took, I'm willing to bet you saw something very much like these two lines of code less than 10% of the way into the course. Right?

Writing to the Screen

But what if you create a Managed C++ Application in Visual C++ .NET? Here's the little skeleton main() that's created for you:

int _tmain(void)
{
    // TODO: Please replace the sample code below 
    //       with your own.
    Console::WriteLine(S"Hello World");
    return 0;
}

Now, you can paste your cout-using code into this main(), and it will work - after you add the usual include statement:

#include <iostream.h>
// ...
    Console::WriteLine(S"Hello World");
    int x = 3;
    cout << "x is " << x << endl;

You'll get a warning, though:

warning C4995: '_OLD_IOSTREAMS_ARE_DEPRECATED': name was marked as #pragma deprecated

To get rid of it, just use the iostream code from the STL, and bring in the std namespace:

#include <iostream>
using namespace std;

The code will then build and run just fine. But to me, seeing that Console::WriteLine() in the same program as the cout line is a little confusing. What's more, Console::WriteLine() has neat features. It's more like printf in that it uses placeholders in a string to show where variable values should be put. For example, here's some working code from a C# console application:

int x = 3;
Console.WriteLine("x is {0}",x);

The {0} is a placeholder, and the value of the second parameter will end up wherever that placeholder appears. So I want to use Console::WriteLine throughout my Managed C++ application, just as it's used in C#. But if you copy these lines into your Managed C++ application, and change the . to a ::, the application won't build. The compiler error is:

error C2665: 'System::Console::WriteLine' : none of the 19 
overloads can convert parameter 2 from type 'int'
        boxpin.cpp(7): could be 'void System::Console::WriteLine(
System::String __gc *,System::Object __gc *)'
        boxpin.cpp(7): or       'void System::Console::WriteLine(
System::String __gc *,System::Object __gc * __gc[])'
        while trying to match the argument list '(char [9], int)'

Now, I'm stubborn, and I expect C++ to be able to do everything the other .NET languages can do - and more. (Some of you may have noticed that already.) So why can't this simple little thing work? Well, if all else fails, read the error messages. I have given it an int for the second parameter, and it would like a pointer. In fact, a pointer to a System::Object (or some class derived from that, of course), a pointer to a __gc object. An int is none of those things. You could try passing &x instead of x, that at least would be a pointer, but it's no help.

What WriteLine() wants is a pointer to an object. You can't hand the integer directly to WriteLine(), which (in the interests of genericity) has been written to handle pointers to garbage-collected objects, and nothing else. Why? Everything in the Base Class Library is designed to work with objects, because they can have member functions - not all .NET languages support the idea of casting or of overloading casting operators. For example, objects that inherit from System::Object all have a ToString() method. You don't want to write a class to hold this little not-an-object integer, and write a ToString() overload for it, plus deal with getting your integer into (and possibly out of) the class whenever you pass it to a Base Class Library method like WriteLine(). So how do you get your integer to WriteLine()?

The __box keyword

Managed C++ is also called Managed Extensions for C++. The word Extensions refers to the extra keywords, all starting with a double underscore, that have been added to the language. Like all keywords that start with a double underscore, they are compiler-specific - don't try these in Visual C++ 6 or a C++ compiler from any other vendor. You've just seen __gc in the error message when trying to compile the WriteLine() call. It stands for Garbage Collected and refers to an object that lives on the managed heap and is managed by the runtime. The __box keyword is the solution to the problem I've just presented about passing an integer to a Base Class Library method that's expecting a System::Object __gc * instead of an int. Here's how to use it:

Console::WriteLine("x is {0}",__box(x));

Boxing a value type means putting the value inside a temporary object, an instance of a class that inherits from System::Object and lives on the garbage collected heap, and passing the address of that temporary object to the method call. Whatever was in the original variable is bitwise-copied into the temporary object, and the temporary object provides all the functionality that WriteLine() will want. The __box keyword means that every service the Base Class Library provides will work with value types as well as with managed types.

Alternatives to Boxing

So boxing allows you to use both value types and managed types with Base Class Library methods that are expecting pointers to managed types. That naturally raises the question: what's the difference between a value type and a managed type? A managed type lives on the garbage collection heap and is managed by the runtime. Here's an example:

__gc class Foo
{
  // internals omitted
};

// ...

Foo* f = new Foo();

The Foo class is a managed type. You can't create instances of it on the stack, like this:

Foo f2;

If you have a class already, perhaps from some pre-.NET application, it will not be a managed type. It doesn't have the __gc keyword. You could add the keyword (assuming the class meets all the rules for being a managed type - more on that in a future column) but then you would have to find all the places that created instances of the class and make sure they were creating the instance on the heap, like this:

OldClass* poc = new OldClass(); //maybe some parameters 
                                //to the constructor

And everywhere your code was calling methods of the class, you'd have to remember to change . to -> -- how tedious! Better to keep the class as an unmanaged class. You can allocate instances on the stack or the unmanaged heap as you prefer:

class notmanaged
{
private:
  int val;
public:
  notmanaged(int v) : val(v) {};
};

// ...
  notmanaged nm(4);
  notmanaged *p = new notmanaged(5);

This is no big deal: it's just how C++ was before Managed Extensions were added with the release of Visual C++ .NET. But let's say you want to pass one of those instances to good old WriteLine():

Console::WriteLine("notmanaged holds  {0}",nm);

You'll get the same error message as before: WriteLine() is no happier with this old-fashioned style class than it was with the integer. "Aha," you think, "I've learned a new keyword, I know what to do:"

Console::WriteLine("notmanaged holds  {0}",__box(nm));

This gets you the rather obscure message "only value types can be boxed." Isn't notmanaged a value type? Well it isn't a managed type, but it doesn't inherit from the base class System::ValueType, and that's necessary to be boxed. Now, there is a handy __value keyword, which will cause the class to inherit from System::ValueType and be boxable, but for classes and structures I don't think boxing is the way to go. You don't just want the bits of data that are in your class or structure to be blurted out onto the screen. You want some control over the way your class is represented as a string - or to be accurate, as a System::String. In other words, you want to write a function so that you retain control of the way your class behaves. So why not add a ToString() method?

class notmanaged
{
private:
  int val;
public:
  notmanaged(int v) : val(v) {};
  String* ToString() {return __box(val)->ToString();}
};
// ...
notmanaged nm(4);
Console::WriteLine("notmanaged holds  {0}",nm.ToString());
notmanaged *p = new notmanaged(5);
Console::WriteLine("notmanaged pointer has {0}",p->ToString());

Note the clever use of __box inside the ToString() method so that it can return a pointer to the managed type System::String* -- and WriteLine() is happy as can be with that as a parameter. You could easily expand this approach for a class with multiple member variables. Of course, you can call the method whatever you like, but calling it ToString will help other .NET programmers, since that's the name throughout the Base Class Library for a method that represents the internals of a class as a single System::String.

Unboxing

So far, you've only seen the __box keyword used in temporary circumstances, just to pass to a method. But you can create a longer-lived pointer to a managed type, like this:

__box int* bx = __box(x);

It's important to remember that this is a copy. If you change it, the original is unchanged. These four lines:

__box int* bx = __box(x);
*bx = 7;
Console::WriteLine("bx is {0}", bx);
Console::WriteLine("x is still {0}", __box(x));

produce this output:

bx is 7
x is still 3

If you want changes in the boxed copy to go back to the original, just dereference the pointer:

x = *bx;

Then x will contain the new value (7, in this example) that you put into the boxed copy.

Summary

Well, that was a long walk in the park just to get a handful of integers onto the screen, wasn't it? Understanding the differences between managed and unmanaged types is vital to keeping your sanity when working in Managed C++. Boxing an unmanaged type is a useful way to convert it to a managed pointer, but when your unmanaged type is a class, remember that you're in control and can make managed pointers however you choose to do so - and a member function called ToString is an excellent approach. And you can unbox without any special keywords - just get the value back out. So don't let pesky compiler errors keep you from exploring all the fun of the Base Class Library!

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.



Comments

  • Boxing of non-POD types

    Posted by kirants on 08/30/2005 08:28pm

    Is boxing useful only in case where data is POD type ?

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds