Handle Types |
|
When studying pointers, we saw that the compiler reserved an area in 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. 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 that heap, the compiler sets aside its own portion of heap 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 the managed heap:
int main() { CProperty victorian; victorian.Value = 485565; CProperty % single = victorian; return 0; } You can then access the members of either the referenced or the referencing variables. If you change the values of the members of the variable, the values of the reference would be changed also. In the same way, if you change the values of the reference, the values of the referenced variable would be changed also. 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; public value class CProperty { public: __wchar_t TypeOfHome; int NumberOfBedrooms; double NumberOfBathrooms; Byte Stories; int YearBuilt; double Value; }; int main() { CProperty ^ townhome; 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 operator ^ can have: CProperty^ townhome; CProperty ^ townhome; CProperty ^townhome; The compiler will assume the same. In this case, townhome is a handle to CProperty. 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: int main() { CProperty ^ townhome = nullptr; Console::WriteLine(); return 0; } After creating a handle, you should let the compiler know what variable it handles. To do this, assign it a declared variable preceded with the % operator, as we did for the & operator when referencing a pointer. Here is an example: using namespace System; public value class CProperty { public: __wchar_t TypeOfHome; int NumberOfBedrooms; double NumberOfBathrooms; Byte Stories; int YearBuilt; double Value; }; int main() { CProperty townhouse; townhouse.YearBuilt = 1986; townhouse.NumberOfBathrooms = 1.5; townhouse.Stories = 3; townhouse.Value = 348255; townhouse.NumberOfBedrooms = 3; townhouse.TypeOfHome = L'T'; CProperty ^ townhome = %townhouse; Console::WriteLine(); return 0; }
In Lesson 4, we saw that you could create a class using the value keyword on the left of the struct or class keyword. If you create a class using the value keyword, when you declare a variable from that class, we have seen so far that you could store it in the stack by declaring it as a value type. Here is an example: using namespace System; public value class CProperty { public: __wchar_t TypeOfHome; int NumberOfBedrooms; double NumberOfBathrooms; Byte Stories; int YearBuilt; double Value; }; int main() { CProperty victorian; victorian.Value = 485565; Console::Write("Property Value: "); Console::WriteLine(victorian.Value); Console::WriteLine(); return 0; } Alternatively, you can store the variable in the native heap. To do this, declare it as a pointer. Remember that if you declare the variable as a pointer, it is stored in the native heap and you must remember to reclaim its memory area when the program ends. This is done using the delete operator: using namespace System; public value class CProperty { public: __wchar_t TypeOfHome; int NumberOfBedrooms; double NumberOfBathrooms; Byte Stories; int YearBuilt; double Value; }; int main() { CProperty * victorian = new CProperty; victorian->Value = 485565; Console::Write("Property Value: "); Console::WriteLine(victorian->Value); delete victorian ; Console::WriteLine(); return 0; } If you want, you can store the variable in the managed heap by creating it as a handle: using namespace System; public value class CProperty { public: __wchar_t TypeOfHome; int NumberOfBedrooms; double NumberOfBathrooms; Byte Stories; int YearBuilt; double Value; }; int main() { CProperty ^ victorian = gcnew CProperty; victorian->Value = 485565; Console::Write("Property Value: "); Console::WriteLine(victorian->Value); Console::WriteLine(); return 0; } As seen already, you can also create a reference to a class type. This means that you have a choice of storing the variable in the stack, the native heap, or the managed heap. Besides the value keyword, you can create a class using the ref keyword. Here is an example: public ref class CProperty { public: __wchar_t TypeOfHome; int NumberOfBedrooms; double NumberOfBathrooms; Byte Stories; int YearBuilt; double Value; }; In most cases, you can use a ref class as you would a value class. There are still many differences between both types. One of the rules you must observes is that you cannot create a native reference of ref class. For example, the following program will compile: using namespace System; public value class CProperty { public: __wchar_t TypeOfHome; int NumberOfBedrooms; double NumberOfBathrooms; Byte Stories; int YearBuilt; double Value; }; int main() { CProperty victorian; victorian.Value = 485565; CProperty & single = victorian; Console::Write("Property Value: "); Console::WriteLine(victorian.Value); Console::Write("Property Value: "); Console::WriteLine(single.Value); Console::WriteLine(); return 0; } The following will not compile: using namespace System; public ref class CProperty { public: __wchar_t TypeOfHome; int NumberOfBedrooms; double NumberOfBathrooms; Byte Stories; int YearBuilt; double Value; }; int main() { CProperty victorian; victorian.Value = 485565; CProperty & single = victorian; Console::Write("Property Value: "); Console::WriteLine(victorian.Value); Console::Write("Property Value: "); Console::WriteLine(single.Value); Console::WriteLine(); return 0; } When compiling it in Microsoft Visual C++ 2005, this program would produce a C3699, a C2440, and a C2228 errors: error C3699: '&' : cannot use this indirection on type 'CProperty' compiler replacing '&' with '^' to continue parsing error C2440: 'initializing' : cannot convert from 'CProperty' to 'CProperty ^' No user-defined-conversion operator available, or No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called .\Exercise.cpp(26) : error C2228: left of '.Value' must have class/struct/union type is 'CProperty ^' did you intend to use '->' instead? The reason this will not compile is that, if you create a class as ref or if you use a class that was created as ref, you cannot create a native reference of it. If you want to create a reference and if you are the one creating a class, make it a value type. If you created the class as ref, or if you are using a ref class that was created by someone else, you can create a tracking reference of it. Based on this, the following instead will work: using namespace System; public ref class CProperty { public: __wchar_t TypeOfHome; int NumberOfBedrooms; double NumberOfBathrooms; Byte Stories; int YearBuilt; double Value; }; int main() { CProperty victorian; victorian.Value = 485565; CProperty % single = victorian; Console::Write("Property Value: "); Console::WriteLine(victorian.Value); Console::Write("Property Value: "); Console::WriteLine(single.Value); Console::WriteLine(); return 0; } Remember that you can also create a handle to use it.
After creating a handle, you can access its members. As done for the pointer, you have two alternatives. You can use the asterisk "*" on the left side of a handle. After including the asterisk and the handle between parentheses, you can use the period operator to access each member. This allows you either to initialize the member or to retrieve its current value. Here are examples: using namespace System; public value class CProperty { public: __wchar_t TypeOfHome; int NumberOfBedrooms; double NumberOfBathrooms; Byte Stories; int YearBuilt; double Value; }; int main() { CProperty apartment; apartment.YearBuilt = 2002; apartment.NumberOfBathrooms = 1; apartment.Stories = 18; apartment.Value = 155825; apartment.NumberOfBedrooms = 2; apartment.TypeOfHome = L'C'; CProperty ^ condo = %apartment; Console::Write("Type of Home: "); Console::WriteLine((*condo).TypeOfHome); Console::Write("Number of Bedrooms: "); Console::WriteLine((*condo).NumberOfBedrooms); Console::Write("Number of Bathrooms: "); Console::WriteLine((*condo).NumberOfBathrooms); Console::Write("Number of Stories: "); Console::WriteLine((*condo).Stories); Console::Write("Year Built: "); Console::WriteLine((*condo).YearBuilt); Console::Write("Monetary Value: "); Console::WriteLine((*condo).Value); Console::WriteLine(); return 0; } Instead of the asterisk, you can use the arrow, as done for the pointer, to access the members of a handle. The above program can produce the same results with: using namespace System; public value class CProperty { public: __wchar_t TypeOfHome; int NumberOfBedrooms; double NumberOfBathrooms; Byte Stories; int YearBuilt; double Value; }; int main() { CProperty apartment; apartment.YearBuilt = 2002; apartment.NumberOfBathrooms = 1; apartment.Stories = 18; apartment.Value = 155825; apartment.NumberOfBedrooms = 2; apartment.TypeOfHome = L'C'; CProperty ^ condo = %apartment; Console::Write("Type of Home: "); Console::WriteLine(condo->TypeOfHome); Console::Write("Number of Bedrooms: "); Console::WriteLine(condo->NumberOfBedrooms); Console::Write("Number of Bathrooms: "); Console::WriteLine(condo->NumberOfBathrooms); Console::Write("Number of Stories: "); Console::WriteLine(condo->Stories); Console::Write("Year Built: "); Console::WriteLine(condo->YearBuilt); Console::Write("Monetary Value: "); Console::WriteLine(condo->Value); Console::WriteLine(); return 0; } Both notations produce the same result: Type of Home: C Number of Bedrooms: 2 Number of Bathrooms: 1 Number of Stories: 18 Year Built: 2002 Monetary Value: 155825 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 to use pointers, you could forget to delete pointers, 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 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: ClassName ^ HandleName = gcnew ClassName; The ClassName is the class whose handle you want to create. The ^, =, and gcnew operators are required. The HandleName is the name of the handle that will be created. After creating the handle, you can assign the desired value to each of its members. Here are examples: using namespace System; public value class CProperty { public: __wchar_t TypeOfHome; int NumberOfBedrooms; double NumberOfBathrooms; Byte Stories; int YearBuilt; double Value; }; int main() { CProperty ^condominium = gcnew CProperty; condominium->YearBuilt = 2002; condominium->NumberOfBathrooms = 1; condominium->Stories = 18; condominium->Value = 155825; condominium->NumberOfBedrooms = 2; condominium->TypeOfHome = L'C'; Console::Write("Type of Home: "); Console::WriteLine(condominium->TypeOfHome); Console::Write("Number of Bedrooms: "); Console::WriteLine(condominium->NumberOfBedrooms); Console::Write("Number of Bathrooms: "); Console::WriteLine(condominium->NumberOfBathrooms); Console::Write("Number of Stories: "); Console::WriteLine(condominium->Stories); Console::Write("Year Built: "); Console::WriteLine(condominium->YearBuilt); Console::Write("Monetary Value: "); Console::WriteLine(condominium->Value); Console::WriteLine(); return 0; } Unlike a pointer whose memory was allocated with the new operator, when a handle is not used anymore, you don't need to de-allocate its memory. The garbage collector would take care of it. If you insist, you can still use the delete operator to remove the handle from memory. You use the delete operator the exact same way we did for the pointer.
|
|
||
Previous | Copyright © 2006-2007 FunctionX, Inc. | Next |
|