Handle Types |
|
When studying pointers, we saw that the compiler reserved an area in the heap memory for the pointers of your application. We also saw that, when you declared a pointer and allocated memory using the new operator, you should remember to de-allocate the memory using the delete operator. Although modern compilers are equipped to check this, if you are working with one that would not empty the memory for you, your program could create a memory leak. The error being human, this has been one of the regular concerns when programming with C and C++. One of the improvements of C++/CLI was to assist you with this issue of managing the memory space that your application would use. If you (decide to) use pointers in your program, the heap memory is highly under your control. Notice that we use "if". This means that, in C++, you can create a complete program that doesn't or hardly use pointers. For a serious programmer, there is no way you can simply decide not to use pointers at all. Since you are going to use them, as we mentioned, the compiler reserves some heap memory for you. If you don't manage it right, you can create a mess. To avoid this, no more "if", the common language runtime (CLR) suggests and sometimes requires that you use another technique. This time, instead of your being in control of the heap, the compiler sets aside its own portion of heap memory and becomes in charge of it. To differentiate both portions of memory, the heap we mentioned when studying pointers is referred to as Native Heap. The heap used by the CLR is called Managed Heap:
To create a tracking reference, declare the variable but type the % operator between the data type and the name of the variable. Here is an exaple: double % HourlyWage To indicate the variable it is referencing, assign it the variable that was previously defined. Here is an example: double % HourlyWage = HourlySalary; After creating and initializing a tracking reference, you can access its value. You can access the variable and its reference using their names. Here is an example: using namespace System; int main() { double HourlySalary = 24.68; double % HourlyWage = HourlySalary; Console::WriteLine("Payroll Preparation"); Console::Write("Hourly Salary: "); Console::WriteLine(HourlySalary); Console::Write("Hourly Wage: "); Console::WriteLine(HourlyWage); Console::WriteLine(); return 0; } The tracking reference and the variable it's referencing would always hold the same value: if you change the value of one, the other's would be changed also. This is illustrated in the following program: using namespace System; int main() { double HourlySalary = 24.68; double % HourlyWage = HourlySalary; Console::WriteLine("Payroll Preparation"); Console::Write("Hourly Salary: "); Console::WriteLine(HourlySalary); Console::Write("Hourly Wage: "); Console::WriteLine(HourlyWage); HourlySalary = 15.25; Console::WriteLine("\nNew Payroll Information"); Console::Write("Hourly Salary: "); Console::WriteLine(HourlySalary); Console::Write("Hourly Wage: "); Console::WriteLine(HourlyWage); Console::WriteLine(); return 0; } This would produce: Payroll Preparation Hourly Salary: 24.68 Hourly Wage: 24.68 New Payroll Information Hourly Salary: 15.25 Hourly Wage: 15.25 Press any key to continue . . .
Besides the native heap, when your application starts, the common language runtime (CLR) reserves another portion of memory that it is in charge of managing. This portion of memory is called the managed heap. To store a variable in the managed heap, you must create it as a handle. To create a handle, as opposed to the asterisk "*" of a pointer, you use the cap "^" operator. Here is an example: using namespace System; int main() { double ^ ItemPrice; Console::WriteLine(); return 0; } As mentioned for the ampersand & operator of the reference and the asterisk operator of the pointer, there are three basic positions the cap ^ can have: double^ ItemPrice; double ^ ItemPrice; double ^ItemPrice; The compiler will assume the same. In this case, ItemPrice is a handle to double. After creating a handle, the compiler reserves an amount of space in the managed heap memory for the variable but leaves that space empty. To access the value held by a handle, you can simply use its name. Here is an example: using namespace System; int main() { double ^ ItemPrice; Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); Console::WriteLine(); return 0; } This would produce: Department Store Item Price: Press any key to continue . . . Notice that the handle holds a null value. When studying pointers, we mentioned that you could not initialize a newly created pointer with just any value. You could initialize it with 0, which represented the null value. To initialize a handle with a null value, you can assign it the nullptr value. Here is an example: using namespace System; int main() { double ^ ItemPrice = nullptr; Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); Console::WriteLine(); return 0; } This would produce: Department Store Item Price: Press any key to continue . . . You assign the nullptr if you don't have or don't know the value that the handle holds. In this case, the compiler would only reserve an amount of space in the managed heap for the variable but it would leave it empty. After creating a handle, instead of keeping its memory empty, you can store a value in it. To do this, simply assign it a value, either when declaring the variable or after. Here is an example: using namespace System; int main() { double ^ ItemPrice = nullptr; Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); ItemPrice = 148.95; Console::Write("Marked Price: "); Console::WriteLine(ItemPrice); Console::WriteLine(); return 0; } This would produce: Department Store Item Price: Marked Price: 148.95 Press any key to continue . . . We saw that there were some details related to using the native heap: you could hesitate to use or not use a pointer, you could forget to delete a pointer, you could even misuse it. To solve these and some other problems, the common language runtime reserves its own portion of the garbage collected heap to make it available when you use handles. Based on this, when creating a handle, to allocate memory for it, you can instantiate it using the gcnew operator. The formula to use it is: DataType ^ VariableName = gcnew DataType; The DataType is the type whose handle you want to create. The ^, =, and gcnew operators are required. The VariableName is the name of the variable that will be created. Here is an example: double ^ DiscountRate = gcnew double; After creating the handle, you can access it using its name. Here is an example: using namespace System; int main() { double ^ ItemPrice = 148.95; double ^ DiscountRate = gcnew double; Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); Console::Write("Discount Rate: "); Console::Write(DiscountRate); Console::WriteLine("%"); Console::WriteLine(); return 0; } You can also access the handle by type the asterisk operator to the left of its name. Here is an example: using namespace System; int main() { double ^ ItemPrice = 148.95; double ^ DiscountRate = gcnew double; Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); Console::Write("Discount Rate: "); Console::Write(*DiscountRate); Console::WriteLine("%"); Console::WriteLine(); return 0; } In both cases, the program would produce: Department Store Item Price: 148.95 Discount Rate: 0% Press any key to continue . . . Notice that, when using the gcnew operator, the memory allocated for the handle is initially set to 0 instead of being left empty. After declaring the variable, you can assign it a value. You can assign the value to its name: using namespace System; int main() { double ^ ItemPrice = 148.95; double ^ DiscountRate = gcnew double; DiscountRate = 0.20; Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); Console::Write("Discount Rate: "); Console::Write(*DiscountRate * 100); Console::WriteLine("%"); Console::WriteLine(); return 0; } You can also assign the value using its name equipped with an asterisk to its left. Here is an example: using namespace System; int main() { double ^ ItemPrice = 148.95; double ^ DiscountRate = gcnew double; *DiscountRate = 0.20; Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); Console::Write("Discount Rate: "); Console::Write(*DiscountRate * 100); Console::WriteLine("%"); Console::WriteLine(); return 0; } In both cases, the program would produce: Department Store Item Price: 148.95 Discount Rate: 20% Press any key to continue . . . Another option (that will become clear when we study classes and their constructors) you can use is to include the desired value in parentheses that follow the data type after the gcnew operator. Here is an example: using namespace System; int main() { double ^ ItemPrice = 148.95; double ^ DiscountRate = gcnew double(0.20); Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); Console::Write("Discount Rate: "); Console::Write(*DiscountRate * 100); Console::WriteLine("%"); Console::WriteLine(); return 0; } If you want to involve a handle in an operation, remember to access it using the asterisk operator. Here are two examples: using namespace System; int main() { double ^ ItemPrice = 148.95; double ^ DiscountRate = gcnew double(0.20); double ^ DiscountAmount = *ItemPrice * *DiscountRate; Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); Console::Write("Discount Rate: "); Console::Write(*DiscountRate * 100); Console::WriteLine("%"); Console::Write("Discount Amount: "); Console::WriteLine(DiscountAmount); delete DiscountRate; Console::WriteLine(); return 0; } Like a pointer whose memory is allocated with the new operator, when a handle is not used anymore, you can reclaim its memory by using the delete operator. Here is an example: using namespace System; int main() { double ^ ItemPrice = 148.95; double ^ DiscountRate = gcnew double(0.20); Console::WriteLine("Department Store"); Console::Write("Item Price: "); Console::WriteLine(ItemPrice); Console::Write("Discount Rate: "); Console::Write(*DiscountRate * 100); Console::WriteLine("%"); delete DiscountRate; Console::WriteLine(); return 0; } Unlike a pointer, you don't need to de-allocate a handle's memory. The garbage collector would take care of it.
|
|
||
Previous | Copyright © 2006-2016, FunctionX, Inc. | Next |
|