Introduction to C++/CLI

  Windows IT Pro
Windows IT Library
  - Advertise        
Windows IT Pro Logo

  Home  |   Books  |   Chapters  |   Topics  |   Authors  |   Book Reviews  |   Whitepapers  |   About Us  |   Contact Us  |   ITTV  |   IT Jobs

search for  on    power search   help
 






Introduction to C++/CLI
View the book table of contents
Author: Nishant Sivakumar
Published: April 2007
Copyright: 2007
Publisher: Manning Publications Co.
 


1.4 HANDLES: THE CLI EQUIVALENT TO POINTERS

Handles are a new concept introduced in C++/CLI; they replace the __gc pointer concept used in Managed C++. Earlier in the chapter, we discussed the pointerusage confusion that prevailed in the old syntax. Handles solve that confusion. In my opinion, the concept of handles has contributed the most in escalating C++ as a first-class citizen of the .NET programming language world. In this section, we’ll look at the syntax for using handles. We’ll also cover the related topic of using tracking references.

1.4.1 Syntax for using handles

A handle is a reference to a managed object on the CLI heap and is represented by the ^ punctuator (pronounced hat).

NOTE:
When I say punctuator in this chapter, I’m talking from a compiler perspective. As far as the language syntax is concerned, you can replace the word punctuator with operator and retain the same meaning.

Handles are to the CLI heap what native pointers are to the native C++ heap; and just as you use pointers with heap-allocated native objects, you use handles with managed objects allocated on the CLI heap. Be aware that although native pointers need not always necessarily point to the native heap (you could get a native pointer pointing to the managed heap or to non-C++ allocated memory storage), managed handles have a close-knit relationship with the managed heap. The following code snippet shows how handles can be declared and used:
String^ str = "Hello world";
Student^ student = Class::GetStudent("Nish");
student->SelectSubject(150);
In the code, str is a handle to a System::String object on the CLI heap, student is a handle to a Student object, and SelectSubject invokes a method on the student handle.

The memory address that str refers to isn’t guaranteed to remain constant. The String object may be moved around after a garbage-collection cycle, but str will continue to be a reference to the same System::String object (unless it’s programmatically changed). This ability of a handle to change its internal memory address when the object it has a reference to is moved around on the CLI heap is called tracking.

Handles may look deceitfully similar to pointers, but they are totally different entities when it comes to behavior. Table 1.4 illustrates the differences between handles and pointers.

Despite all those differences, typically you’ll find that for most purposes, you’ll end up using handles much the same way you would use pointers. In fact, the * and -> operators are used to dereference a handle (just as with a pointer). But it’s important to be aware of the differences between handles and pointers. The VC++ team members initially called them managed pointers, GC pointers, and tracking pointers. Eventually, the team decided to call them handles to avoid confusion with pointers; in my opinion, that was a smart decision.

Now that we’ve covered handles, it’s time to introduce the associated concept of tracking references.

1.4.2 Tracking references

Just as standard C++ supports references (using the & punctuator) to complement pointers, C++/CLI supports tracking references that use the % punctuator to complement handles. The standard C++ reference obviously can’t be used with a managed object on the CLR heap, because it’s not guaranteed to remain in the same memory address for any period of time. The tracking reference had to be introduced; and, as the name suggests, it tracks a managed object on the CLR heap. Even if the object is moved around by the GC, the tracking reference will still hold a reference to it. Just as a native reference can bind to an l-value, a tracking reference can bind to a managed l-value. And interestingly, by virtue of the fact that an l-value implicitly converts to a managed l-value, a tracking reference can bind to native pointers and class types, too. Let’s look at a function that accepts a String^ argument and then assigns a string to it. The first version doesn’t work as expected; the calling code finds that the String object it passed to the function hasn’t been changed:
void ChangeString(String^ str)
{
    str = "New string";
}
int main(array<System::String^>^ args)
{
    String^ str = "Old string";
    ChangeString(str);
    Console::WriteLine(str);
}
If you execute this code snippet, you’ll see that str contains the old string after the call to ChangeString. Change ChangeString to
void ChangeString(String^% str)
{
     str = "New string";
}
You’ll now see that str does get changed, because the function takes a tracking reference to a handle to a String object instead of a String object, as in the previous case. A generic definition would be to say that for any type T, T% is a tracking reference to type T. C# developers may be interested to know that MSIL-wise, this is equivalent to passing the String as a C# ref argument to ChangeString. Therefore, whenever you want to pass a CLI handle to a function, and you expect the handle itself to be changed within the function, you need to pass a tracking reference to the handle to the function.

In standard C++, in addition to its use in denoting a reference, the & symbol is also used as a unary address-of operator. To keep things uniform, in C++/CLI, the unary % operator returns a handle to its operand, such that the type of %T is T^ (handle to type T). If you plan to use stack semantics (which we’ll discuss in the next chapter), you’ll find yourself applying the unary % operator quite a bit when you access the .NET Framework libraries. This is because the .NET libraries always expect a handle to an object (because C++ is the only language that supports a nonhandle reference type); so, if you have an object declared using stack semantics, you can apply the unary % operator on it to get a handle type that you can pass to the library function. Here’s some code showing how to use the unary % operator:
Student^ s1 = gcnew Student();
Student% s2 = *s1; // Dereference s1 and assign
                   // to the tracking reference s2
Student^ s3 = %s2; // Apply unary % on s2 to return a Student^
Be aware that the * punctuator is used to dereference both pointers and handles, although symmetrically thinking, a ^ punctuator should been used to dereference a handle. Perhaps this was designed this way to allow us to write agnostic template/ generic classes that work on both native and unmanaged types.

You now know how to declare a CLI type; you also know how to use handles to a CLI type. To put these skills to use, you must understand how CLI types are instantiated, which is what we’ll discuss in the next section.


1.5 INSTATIATING CLI CLASSES

In this section, you’ll see how CLI classes are instantiated using the gcnew operator. You’ll also learn how constructors, copy constructors, and assignment operators work with managed types. Although the basic concepts remain the same, the nature of the CLI imposes some behavioral differences in the way constructors and assignment operators work; when you start writing managed classes and libraries, it’s important that you understand those differences. Don’t worry about it, though. Once you’ve seen how managed objects work with constructors and assignment operators, the differences between instantiating managed and native objects will automatically become clear.

1.5.1 The gcnew operator

The gcnew operator is used to instantiate CLI objects. It returns a handle to the newly created object on the CLR heap. Although it’s similar to the new operator, there are some important differences: gcnew has neither an array form nor a placement form, and it can’t be overloaded either globally or specifically to a class. A placement form wouldn’t make a lot of sense for a CLI type, when you consider that the memory is allocated by the Garbage Collector. It’s for the same reason you aren’t permitted to overload the gcnew operator. There is no array form for gcnew because CLI arrays use an entirely different syntax from native arrays, which we’ll cover in detail in the next chapter. If the CLR can’t allocate enough memory for creating the object, a System::OutOfMemoryException is thrown, although chances are slim that you’ll ever run into that situation. (If you do get an OutOfMemoryException, and your system isn’t running low on virtual memory, it’s likely due to badly written code such as an infinite loop that keeps creating objects that are erroneously kept alive.) The following code listing shows a typical usage of the gcnew keyword to instantiate a managed object (in this case, the Student object):
ref class Student
{
...
};
...

Student^ student = gcnew Student();
student->SelectSubject("Math", 97);
The gcnew operator is compiled into the newobj MSIL instruction by the C++/CLI compiler. The newobj MSIL instruction creates a new CLI object—either a ref object on the CLR heap or a value object on the stack—although the C++/CLI compiler uses a different mechanism to handle the usage of the gcnew operator to create value type objects (which I’ll describe later in this section). Because gcnew in C++ translates to newobj in the MSIL, the behavior of gcnew is pretty much dependent on, and therefore similar to, that of the newobj MSIL instruction. In fact, newobj throws System::OutOfMemoryException when it can’t find enough memory to allocate the requested object. Once the object has been allocated on the CLR heap, the constructor is called on this object with zero or more arguments (depending on the constructor overload that was used). On successful completion of the call to the constructor, gcnew returns a handle to the instantiated object. It’s important to note that if the constructor call doesn’t successfully complete, as would be the case if an exception was raised inside the constructor, gcnew won’t return a handle. This can be easily verified with the following code snippet:
ref class Student
{
public:
    Student()
    {
       throw gcnew Exception("hello world");
    }
};

//...

Student^ student = nullptr; //initialize the handle to nullptr

try
{
    student = gcnew Student(); //attempt to create object
}
catch(Exception^)
{
}

if(student == nullptr) //check to see if student is still nullptr
    Console::WriteLine("reference not allocated to handle");
Not surprisingly, student is still nullptr when it executes the if block. Because the constructor didn’t complete executing, the CLR concludes that the object hasn’t fully initialized, and it doesn’t push the handle reference on the stack (as it would if the constructor had completed successfully).

NOTE:
C++/CLI introduces the concept of a universal null literal called nullptr. This lets you use the same literal (nullptr) to represent a null pointer and a null handle value. The nullptr implicitly converts to a pointer or handle type; for the pointer, it evaluates to 0, as dictated by standard C++; for the handle, it evaluates to a null reference. You can use the nullptr in relational, equality, and assignment expressions with both pointers and handles.

As I mentioned earlier, using gcnew to instantiate a value type object generates MSIL that is different from what is generated when you instantiate a ref type. For example, consider the following code, which uses gcnew to instantiate a value type:
value class Marks
{
public:
    int Math;
    int Physics;
    int Chemistry;
};

//...

    Marks^ marks = gcnew Marks();
For this code, the C++/CLI compiler uses the initobj MSIL instruction to create a Marks object on the stack. This object is then boxed to a Marks^ object. We’ll discuss boxing and unboxing in the next section; for now, note that unless it’s imperative to the context of your code to gcnew a value type object, doing so is inefficient. A stack object has to be created, and this must be boxed to a reference object. Not only do you end up creating two objects (one on the managed stack, the other on the managed heap), but you also incur the cost of boxing. The more efficient way to create an object of type Marks (or any value type) is to declare it on the stack, as follows:
Marks marks;
You’ve seen how calling gcnew calls the constructor on the instance of the type being created. In the coming section, we’ll take a more involved look at how constructors work with CLI types.

If you have a ref class, and you haven’t written a default constructor, the compiler generates one for you. In MSIL, the constructor is a specially-named instance method called .ctor. The default constructor that is generated for you calls the constructor of the immediate base class for the current class. If you haven’t specified a base class, it calls the System::Object constructor, because every ref object implicitly derives from System::Object. For example, consider the following two classes, neither of which has a user-defined constructor:
ref class StudentBase
{
};
ref class Student: StudentBase
{
};
Neither Student nor StudentBase has a user-provided default constructor, but the compiler generates constructors for them. You can use a tool such as ildasm.exe (the IL Disassembler that comes with the .NET Framework) to examine the generated MSIL. If you do that, you’ll observe that the generated constructor for Student calls the constructor for the StudentBase object:
call instance void StudentBase::.ctor()
The generated constructor for StudentBase calls the System::Object constructor:
call instance void [mscorlib]System.Object::.ctor()
Just as with standard C++, if you have a constructor—either a default constructor or one that takes one or more arguments—the compiler won’t generate a default constructor for you. In addition to instance constructors, ref classes also support static constructors (not available in standard C++). A static constructor, if present, initializes the static members of a class. Static constructors can’t have parameters, must also be private, and are automatically called by the CLR. In MSIL, static constructors are represented by a specially named static method called .cctor. One possible reason both special methods have a . in their names is that this avoids name clashes, because none of the CLI languages allow a . in a function name. If you have at least one static field in your class, the compiler generates a default static constructor for you if you don’t include one on your own. When you have a simple class, such as the following, the generated MSIL will have a static constructor even though you haven’t specified one:
ref class StudentBase
{
    static int number;
};
Due to the compiler-generated constructors and the implicit derivation from System::Object, the generated class looks more like this:
ref class StudentBase : System::Object
{
    static int number;
    StudentBase() : System::Object()
    {
    }
    static StudentBase()
    {
    }
};
A value type can’t declare a default constructor because the CLR can’t guarantee that any default constructors on value types will be called appropriately, although members are 0-initialized automatically by the CLR. In any case, a value type should be a simple type that exhibits value semantics, and it shouldn’t need the complexity of a default constructor—or even a destructor, for that matter. Note that in addition to not allowing default constructors, value types can’t have userdefined destructors, copy constructors, and copy-assignment operators.

Before you end up concluding that value types are useless, you need to think of value types as the POD equivalents in the .NET world. Use value types just as you’d use primitive types, such as ints and chars, and you should be OK. When you need simple types, without the complexities of virtual functions, constructors and operators, value types are the more efficient option, because they’re allocated on the stack. Stack access will be faster than accessing an object from the garbagecollected CLR heap. If you’re wondering why this is so, the stack implementation is far simpler when compared to the CLR heap. When you consider that the CLR heap also intrinsically supports a complex garbage-collection algorithm, it becomes obvious that the stack object is more efficient.

It must be a tad confusing when I mention how value types behave differently from reference types in certain situations. But as a developer, you should be able to distinguish the conceptual differences between value types and reference types, especially when you design complex class hierarchies. As we progress through this book and see more examples, you should feel more comfortable with these differences.

Because we’ve already talked about constructors, we’ll discuss copy constructors next.

1.5.3 Copy constructors

A copy constructor is one that instantiates an object by creating a copy of another object. The C++ compiler generates a copy constructor for your native classes, even if you haven’t explicitly done so. This isn’t the case for managed classes. Consider the following bit of code, which attempts to copy-construct a ref object:
ref class Student
{
};

int main(array<System::String^>^ args)
{
    Student^ s1 = gcnew Student();
    Student^ s2 = gcnew Student(s1);
}  
If you run that through the compiler , you’ll get compiler error C3673 (class does not have a copy-constructor). The reason for this error is that, unlike in standard C++, the compiler won’t generate a default copy constructor for your class. At least one reason is that all ref objects implicitly derive from System::Object, which doesn’t have a copy constructor. Even if the compiler attempted to generate a copy constructor for a ref type, it would fail, because it wouldn’t be able to access the base class copy constructor (it doesn’t exist).

To make that clearer, think of a native C++ class Base with a private copy constructor, and a derived class Derived (that publicly inherits from Base). Attempting to copy-construct a Derived object will fail because the base class copy constructor is inaccessible. To demonstrate, let’s write a class that is derived from a base class that has a private copy constructor:
class Base
{
public:
    Base(){}
private:
    Base(const Base&);
};

class Derived : public Base
{
};

int _tmain(int argc, _TCHAR* argv[])
{
    Derived d1;  
    Derived d2(d1); // <-- won't compile
}
Because the base object’s copy constructor is declared as private and therefore is inaccessible from the derived object, this code won’t compile: The compiler is unable to copy-construct the derived object. What happens with a ref class is similar to this code. In addition, unlike native C++ objects, which aren’t polymorphic unless you access them via a pointer, ref objects are implicitly polymorphic (because they’re always accessed via reference handles to the CLR heap). This means a compiler-generated copy constructor may not always do what you expect it to do. When you consider that ref types may contain member ref types, there is the question of whether a copy constructor implements shallow copy or deep copy for those members. The VC++ team presumably decided that there were too many equations to have the compiler automatically generate copy constructors for classes that don’t define them.

If you want copy-construction support for your class, you must implement it explicitly, which fortunately isn’t a difficult task. Let’s add a copy constructor to the Student class:
ref class Student
{
public:
    Student(){}
    Student(const Student^)
    {
    }
};
That wasn’t all that tough, was it? Notice how you have to explicitly add a default parameterless constructor to the class. This is because it won’t be generated by the compiler when the compiler sees that there is another constructor present. One limitation with this copy constructor is that the parameter has to be a Student^, which is OK except that you may have a Student object that you want to pass to the copy constructor. If you’re wondering how that’s possible, C++/CLI supports stack semantics, which we’ll cover in detail in chapter 3. Assume that you have a Student object s1 instead of a Student^, and you need to use that to invoke a copy constructor:
Student s1;
Student^ s2 = gcnew Student(s1); //error C3073
As you can see, that code won’t compile. There are two ways to resolve the problem. One way is to use the unary % operator on the s1 object to get a handle to the Student object:
Student s1;
Student^ s2 = gcnew Student(%s1);
Although that compiles and solves the immediate problem, it isn’t a complete solution when you consider that every caller of your code needs to do the same thing if they have a Student object instead of a Student^. An alternate solution is to have two overloads for the copy constructor, as shown in Listing 1.2.
This solves the issue of a caller requiring the right form of the object, but it brings with it another problem: code duplication. You could wrap the common code in a private method and have both overloads of the copy constructor call this method, but then you couldn’t take advantage of initialization lists.

Eventually, it’s a design choice you have to make. If you only have the copy constructor overload taking a Student^, then you need to use the unary % operator when you have a Student object; and if you only have the overload taking a Student%, then you need to dereference a Student^ using the * operator before using it in copy construction. If you have both, you may end up with possible code duplication; and the only way to avoid code duplication (using a common function called by both overloads) deprives you of the ability to use initialization lists. My recommendation is to use the overload that takes a handle (in the previous example, the one that takes a Student^), because this overload is visible to other CLI languages such as C# (unlike the other overload)—which is a good thing if you ever run into language interop situations. The unary % operator won’t really slow down your code; it’s just an extra character that you need to type. I also suggest that you stay away from using two overloads, unless it’s a specific case of a library that will be exclusively used by C++ callers; even then, you must consider the issue of code duplication.

Now you know that if you need copy construction on your ref types, you must implement it yourself. So, it may not be surprising to see in the next section that the same holds true for copy-assignment operators.

1.5.4 Assignment operators

The copy-assignment operator is one that the compiler generates automatically for native classes in standard C++, but this isn’t so for a ref class. The reasons are similar to those that dictate that a copy constructor isn’t automatically generated. The following code (using the Student class defined earlier) won’t compile:
Student s1("Nish");
Student s2;
s2 = s1; // error C2582: 'operator =' function
         // is unavailable in 'Student'
Defining an assignment operator is similar to what you do in standard C++, except that the types are managed:
Student% operator=(const Student% s)
{
    m_name = s.m_name;
    return *this;
}
Note that the copy-assignment operator can be used only by C++ callers, because it’s invisible to other languages like C# and VB.NET. Also note that, for handle variables, you don’t need to write a copy-assignment operator, because the handle value is copied over intrinsically.

You should try to bring many of the good C++ programming practices you followed into the CLI world, except where they aren’t applicable. As an example, the assignment operator doesn’t handle self-assignment. Although it doesn’t matter in our specific example, consider the case in >Listing 1.3.

In the preceding listing, assume that Grades is a class with a nontrivial constructor and destructor; thus, in the Student class assignment operator, before the m_grades member is copied, the existing Grades object is explicitly disposed by calling delete on it—all very efficient. Let’s assume that a self-assignment occurs:
while(some_condition)
{
    // studarr is an array of Student objects
    studarr[i++] = studarr[j--]; // self-assignment occurs if i == j
    if(some_other_condition)
    break;
}
In the preceding code snippet, if ever i equals j, you end up with a corrupted Student object with an invalid m_grades member. Just as you would do in standard C++, you should check for self-assignment:
Student% operator=(const Student% s)
{
    if(%s == this) <-- Check for self-assignment
    {
        return *this;     <-- If it is so, return immediately
    }
    m_name = s.m_name;
    if(m_grades)
        delete m_grades;
    m_grades = s.m_grades;
    return *this;
}
We’ve covered some ground in this section—and if you feel that a lot of information has been presented too quickly, don’t worry. Most of the things we’ve discussed so far will come up again throughout this book; eventually, it will all make complete sense to you. We’ll now look at boxing and unboxing, which are concepts that I feel many .NET programmers don’t properly understand—with notso- good consequences.


1.6 BOXING AND UNBOXING

Boxing is the conversion of a value of type V to an object of type V^ on the CLR heap, which is a bit-wise copy of the original value object. Figure 1.4 native cod shows a diagrammatic representation of the boxing process. Unboxing is the reverse process, where an Object^ or a V^ is cast back to the original value type V. Boxing is an implicit process (although it can be explicitly forced, as well), whereas unboxing is always an explicit process. If it sounds confusing, visualize a real box into which you put some object (say, a camera) so that you can send it via FedEx to your friend the next city. The same thing happens in CLR boxing. When your friend receives the package, they open the box and retrieve the camera, which is analogous to CLR unboxing.

In this section, we’ll look at how boxing is an implicit operation in the new C++/ CLI syntax, how boxing ensures type safety, how boxing is implemented at the MSIL level, and how to assign a nullptr to a boxed value type.

1.6.1 Implicit boxing in the new syntax

Whenever you pass a simple type like an int or a char to a method that expects an Object, the int or char is boxed to the CLR heap, and this boxed copy is used by the method. The reason is that ref types are always references to whole objects on the CLR heap, whereas value types are typically on the stack or even on the native C++ heap. When a method expects an Object reference, the value type has to be copied to the CLR heap, where it must behave like a regular ref-type object. In the same way, when the underlying value type has to be retrieved, it must be unboxed back to the original value-type object. The internal boxing and unboxing mechanisms are implemented by the CLR and supported in MSIL, so all the compiler needs to do is emit the corresponding MSIL instructions.

In the old syntax, boxing was an explicit process using the __box keyword. Several programmers complained about the extra typing required. Because most people felt that the double-underscored keywords were repulsive, the fact that they had to use one of those keywords a gratuitous number of times in the course of everyday programming made them all the more upset. You can’t blame them, as the code examples in Table 1.5 show.

It would be an understatement to say that the second code example is a lot more pleasing to the eye and involves much less typing. But implicit boxing has a dangerous disadvantage: It hides the boxing costs involved from the programmer, which can be a bad thing. Boxing is an expensive operation; a new object has to be created on the CLR heap, and the value type must be bitwise copied into this object. Similarly, whenever a lot of boxing is involved, chances are good that quite a bit of unboxing is also being performed. Unboxing typically involves creating the original value type on the managed stack and bitwise copying its data from the boxed object. As a developer, if you ignore the costs of repeated boxing/unboxing operations, either knowingly or unknowingly, you may run into performance issues, most often in applications where performance is a major concern.

1.6.2 Boxing and type-safety

When you box a value type, the boxed copy is a separate entity from the original value type. Changes in one of them won’t be reflected in the other. Consider the following code snippet, where you have an int, a boxed object containing the int, and a second int that has been explicitly unboxed from the boxed object:
int i = 100;
Object^ boxed_i = i; //implicitly boxed to Object^
int j = *safe_cast<int^>(boxed_i); //explicitly unboxed
Console::WriteLine("i={0}, boxed_i={1}, j={2}", i, boxed_i, j);
i++; j--;
Console::WriteLine("i={0}, boxed_i={1}, j={2}", i, boxed_i, j);
The first call to Console::WriteLine outputs
i=100, boxed_i=100, j=100
The second call outputs
i=101, boxed_i=100, j=99
As the output clearly indicates, they’re three different entities: the original value type, the boxed type, and the unboxed value type. Notice how you had to safe_cast the Object^ to an int^ before dereferencing it. This is because dereferencing is always done on the boxed value type. To get an int, you have to apply the dereference operator on an int^, and hence the cast.

NOTE:
The safe_cast operator is new to C++/CLI and replaces __try_cast in the old syntax. safe_cast is guaranteed to produce verifiable MSIL. You can use safe_cast wherever you would typically use dynamic_cast, reinterpret_cast, or static_cast. At runtime, safe_cast checks to see if the cast is valid; if so, it does the conversion or else throws a System::InvalidCastException.

When you box a value type, the boxed value remembers the original value type— which means that if you attempt to unbox to a different type, you’ll get an InvalidCastException. This ensures type-safety when you perform boxing and unboxing operations. Consider Listing 1.4, which demonstrates what happens when you attempt to unbox objects to the wrong value types.

This listing attempts to unbox a boxed double to an int and a boxed int to a double. Although the code compiles, during runtime, an InvalidCastException is thrown:
Unable to cast object of type 'System.Double' to type 'System.Int32'.
Unable to cast object of type 'System.Int32' to type 'System.Double'.
Note that type matching is always exact; for instance, you can’t unbox an int into an __int64 (even though it would be a safe conversion).

1.6.3 Implementation at the MSIL level

MSIL uses the box instruction to perform boxing. The following is a quote from the MSIL documentation: "The box instruction converts the raw valueType (an unboxed value type) into an instance of type Object (of type O). This is accomplished by creating a new object and copying the data from valueType into the newly allocated object."

To get a better idea of how boxing is done, let’s look at how the MSIL is generated (see Table 1.6).

We won’t decipher each MSIL instruction, but the line of code that is of interest is the instruction at location IL_0004: box int32. The instruction before it, ldloc.0, loads the contents of the local variable at the 0th position (which happens to be the int variable i) into the stack. The box instruction creates a new Object, copies the value (from the stack) into this object (using bitwise copy semantics), and pushes a handle to this Object on the stack. The stloc.1 instruction pops this Object from the stack into the local variable at the first position (the Object^ variable o). Table 1.7 shows how unboxing is done at the MSIL level.

Unboxing is the reverse process. The Object to be unboxed is pushed on the stack, and a cast to int^ is performed using the castclass instruction. On successful completion of this call, the Object on the stack is of type int^. The unbox int32 instruction is executed, and it converts the boxed object (on the stack) to a managed pointer to the underlying value type. This behavior is different from boxing where a new object is created; however, unbox doesn’t create a new value type instance. Instead, it returns the address of the underlying value type on the CLR heap. The ldind.i4 instruction indirectly loads the value from the address returned on the stack by the unbox instruction. This is basically a form of dereferencing. Finally, the stloc.0 instruction stores this value in local variable 0, which happens to be the int variable x.

The basic purpose of showing you the generated IL is to give you a better idea of the costs involved in boxing/unboxing operations. When you box, you incur the cost of creating a new Object. When copying the value type into this Object, you waste CPU cycles as well as extra memory. When you unbox, you typically have to safe_cast to your value type’s corresponding handle type, and the runtime has to check to see if it’s a valid cast operation. Once you do that, the actual unboxing reveals the address of the value type object within the CLI object, which has to be dereferenced and the original value copied back.

Thus, both boxing and unboxing are very expensive operations. You probably won’t see much of a performance decrease for simple applications, but bigger and more complex applications may be seriously affected by performance loss if you don’t restrict the number of boxing/unboxing operations that are performed. Because boxing is implicit now, as a programmer you have to be that little bit extra-cautious when you convert value types to ref types, either directly or indirectly, as when you call a method that expects a ref-type argument with a value type.

1.6.4 Assigning null to a boxed value type

An interesting effect of implicit boxing is that you can’t initialize a boxed value type to null by assigning a 0 to it. You have to use the nullptr constant to accomplish that:
int^ x1 = nullptr;
if(!x1)
    Console::WriteLine("x1 is null"); // <-- this line is executed
else
    Console::WriteLine("x1 is not null");

int^ x2 = 0;
if(!x2)
    Console::WriteLine("x2 is null");
else
    Console::WriteLine("x2 is not null");
    // ^-- this line is executed
In the second case, the 0 is treated as an int, which is boxed to an int^. You specifically need to use nullptr if you want to assign a handle to null. Note that the compiler issues warning C4965 (implicit box of integer 0; use nullptr or explicit cast) whenever it can.

When you have two overloads for a function that differ only by a value type argument, with one overload using the value type and the other using the boxed value type, the overload using the value type is given preference:
void Show(int)
{
    Console::WriteLine(__FUNCSIG__);
}
void Show(int^)
{
    Console::WriteLine(__FUNCSIG__);
}
Now, a call such as
Show(75);
will call the Show(int) overload instead of the Show(int^) overload. Keep in mind that, when selecting the best overload, the compiler gives lowest priority to one that requires boxing. If you had another overload that required a nonboxing cast, that overload would receive preference over the one that requires boxing. Given three overloads, one that takes an int, one that takes a double, and one that takes an int^, the order of precedence would be
[first] void Show(int)
[second] void Show(double)
[third] void Show(int^)
To force an overload, you can do a cast to the argument type for that overload:
Show(static_cast(75));
Now that we’ve covered boxing and unboxing, I suggest that you always consciously keep track of the amount of boxing that is done in your code. Because it’s an implicit operation, you may miss out on intensive boxing operations; but where boxing occurs, there is bound to be unboxing, too. If you find yourself having to do a lot of unboxing, review your code to see if it overuses boxing, and try to redesign your class to reduce the amount required. Take special care inside loops, which is where most programmers end up with boxing-related performance issues.


1.7 SUMMARY

In this chapter, we’ve covered some of the fundamental syntactic concepts of the C++/CLI language. As you may have inferred by now, the basic programming concepts remain the same in C++/CLI as in standard C++. However, you need to accommodate for the CLI and everything that comes with it, such as garbage collection, handles to the CLR heap, tracking references, and the implicit derivation from System::Object. Topics we’ve covered included how to declare and instantiate CLI types; how to use handles, and how they differ from pointers; and how boxing and unboxing are performed when converting from value types to reference types.

The designers of C++/CLI have gone to great lengths to ensure as close a similarity to the standard C++ language as is practically possible, but some changes had to be made due to the nature of the CLI (which is a different environment from native C++). As long as you’re aware of those differences and write code accordingly, you’ll do well.

Although C++/CLI’s biggest strength is its ability to compile mixed-mode code, you need to be familiar with the core CLI programming concepts before you can begin writing mixed-mode applications. With that view, in the next couple of chapters we’ll explore the CLI features that are supported by C++/CLI, such as properties, delegates and events, CLI arrays, CLI pointers, stack semantics, function overriding, generics, and managed templates.



Page: 1, 2



ADS BY GOOGLE SPONSORED LINKS FEATURED LINKS

Maximize your SharePoint Investment – 8 Cities
Discover best practices and tips for both architecting and administering SharePoint. Early Bird Price of $99 through Sept 15th.

Find a new job now on the all new IT Job Hound!
Search jobs, post your resume, and set up job e-mail alerts!

Master SharePoint with 3 eLearning Seminars
Learn how to build a better SharePoint infrastructure and enable powerful collaboration with MVPs Dan Holme and Michael Noel. Register today!

Top Tools for Virtualization Disaster Recovery & Replication
View this web seminar on August 14th to learn about two tools that will result in faster backup and restore with P2V disaster recovery.

SharePointConnections Conference Fall 2008
Don’t miss the premier event for Microsoft IT Professionals in Las Vegas, November 10-13. Register and book your room by August 25 and receive a FREE room night (based on a three night minimum stay).

VMworld 2008 - Sign Up Today!
Join your peers on September 15-18 at The Venetian Hotel in Las Vegas as VMware hosts VMworld 2008, the leading Virtualization event.



Entrust Unified Communications Certs
Secure Exchange 2007 and save 20%. Now through Sept. 2008.

Increase Application Performance
Free White Paper by Editor's Best winner, Texas Memory Systems.

Need to convert between XML, DBs, EDI, and Excel? Try MapForce free!
Drag & drop to transform between popular data formats – get results instantly or generate code.

Microsoft® Tech•Ed EMEA 2008 IT Professionals
Advance your thinking with new ideas and practical real-world solutions at Microsoft’s FIVE day technical infrastructure conference 3-7 Nov., 2008. Register before 26 September 2008 to save €300.

Order Your SQL Fundamentals CD Today!
Learn how to use SQL Server, understand Office integration techniques and dive into the essentials of SQL Express and Visual Basic with this free SQL Fundamentals CD.

Are You Really Compliant with Software Regulations?
View this web seminar that will help you with compliance best practices and check out a management solution to assure that you won’t be in jeopardy of an audit.

Virtualization Congress Oct. 14-16 in London
Don't miss Virtualization Congress, the premiere EMEA conference dedicated to hardware, OS and application virtualization. Oct. 14-16.
Windows IT Pro Home Register FAQ for Windows WinInfo News
Europe Edition About Us Contact Us/Customer Service Media Kit Affiliates / Licensing  
SQL Server Magazine Office & SharePoint Pro Windows Dev Pro IT Job Hound ITTV
IT Library Technical Resources Directory Connected Home Windows Excavator Windows SuperSite 
 
 Windows IT Pro is a Division of Penton Media Inc.
 Copyright © 2008 Penton Media, Inc., All rights reserved. Terms and Use | Privacy Statement | Reprints and Licensing