Classes and Memory Management
Classes and Memory Management
Pointers and Classes
Native References
As done for variables of primitive types, you can create a reference to a class using a declared variable. The same rule applies: the object that is used as a reference must have been previously initialized.
Practical Learning: Introducing Pointers and Classes
using namespace System; public value class CProperty { public: __wchar_t TypeOfHome; int Bedrooms; float Bathrooms; Byte Stories; int YearBuilt; double Value; }; int main() { CProperty townhouse; townhouse.YearBuilt = 1986; townhouse.Bathrooms = 1.5f; townhouse.Stories = 3; townhouse.Value = 348255; townhouse.Bedrooms = 3; townhouse.TypeOfHome = L'T'; CProperty &townhome = townhouse; Console::WriteLine("=//= Altair Realty =//="); Console::WriteLine("-=- Properties Inventory -=-"); Console::Write("Type of Home: "); Console::WriteLine(townhome.TypeOfHome); Console::Write("Number of Bedrooms: "); Console::WriteLine(townhome.Bedrooms); Console::Write("Number of Bathrooms: "); Console::WriteLine(townhome.Bathrooms); Console::Write("Number of Stories: "); Console::WriteLine(townhome.Stories); Console::Write("Year Built: "); Console::WriteLine(townhome.YearBuilt); Console::Write("Monetary Value: "); Console::WriteLine(townhome.Value); Console::WriteLine(); return 0; } |
=//= Altair Realty =//= -=- Properties Inventory -=- Type of Home: T Number of Bedrooms: 3 Number of Bathrooms: 1.5 Number of Stories: 3 Year Built: 1986 Monetary Value: 348255 Press any key to continue . . .
As done with variables of the regular data types, you can declare a pointer to a class. Here is an example:
using namespace System; public value struct CSquare { double Side; }; int main() { CSquare *square; return 0; }
After declaring the pointer, you should let the compiler know what variable the pointer is pointing to. This can easily be done by assigning the address of an existing object as follows:
using namespace System; public value struct CSquare { double Side; }; int main() { CSquare sqr; CSquare *square = &sqr; Console::WriteLine(); return 0; }
Practical Learning: Introducing Pointers to Classes |
using namespace System; public value class CProperty { public: __wchar_t TypeOfHome; int Bedrooms; float Bathrooms; Byte Stories; int YearBuilt; double Value; }; int main() { CProperty condominium; CProperty *apartment = &condominium; Console::WriteLine(); return 0; }
Accessing the Members of a Pointer to a Class |
After declaring a pointer to a class, to use it, you can access one, a few, or all of its members. You have a choice between two operators: the period and the arrow.
We saw that the asterisk "*" on the left side of a pointer represented the value of the variable. In the case of a class, when writing *sqr, this represents a CSquare object and it holds the values of the object to which it would have been initialized. To access its members, yon can use the period operator that is used to access the values of an object. Because the period has a higher precedence than the asterisk, you must include the object and its asterisk between parentheses. Here is an example:
using namespace System; public value struct CSquare { double Side; }; int main() { CSquare sqr; sqr.Side = 24.48; CSquare *square = &sqr; Console::WriteLine("Square Characteristics"); Console::Write("Side: "); Console::WriteLine((*square).Side); Console::WriteLine(); return 0; }
As an alternative, you can access the values using the pointer to the class. In this case, you would use the name of the pointer and the arrow operator. Here are examples:
using namespace System; public value struct CSquare { double Side; }; int main() { CSquare sqr; sqr.Side = 24.48; CSquare *square = &sqr; Console::WriteLine("Square Characteristics"); Console::Write("Side: "); Console::WriteLine(square->Side); Console::WriteLine(); return 0; }
Both notations produce the same result:
Square Characteristics Side: 24.48 Press any key to continue . . .
Practical Learning: Accessing the Members of a Pointer to a Class |
using namespace System; public value class CProperty { public: __wchar_t TypeOfHome; int Bedrooms; float Bathrooms; Byte Stories; int YearBuilt; double Value; }; int main() { CProperty apartment; CProperty *condominium = &apartment; condominium->YearBuilt = 1986; condominium->Bathrooms = 1.5f; condominium->Stories = 3; condominium->Value = 348255; condominium->Bedrooms = 3; condominium->TypeOfHome = L'T'; Console::WriteLine("=//= Altair Realty =//="); Console::WriteLine("-=- Properties Inventory -=-"); Console::Write("Type of Home: "); Console::WriteLine(condominium->TypeOfHome); Console::Write("Number of Bedrooms: "); Console::WriteLine(condominium->Bedrooms); Console::Write("Number of Bathrooms: "); Console::WriteLine(condominium->Bathrooms); 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; }
Using a pointer allows you to use memory only as needed. This is done by using the asterisk * and the new operators. The syntax of declaring a pointer to a class is: ClassName* VariableName = new ClassName; The ClassName is the class whose variable you want to declare. It could be in the program or one that shipped with the compiler. Once again, the asterisk lets the compiler know that the object is declared as a pointer. The variable name follows the same rules we have applied to other variables so far. After declaring the pointer, you can access each of its members and assign it an appropriate value. Here are examples: |
using namespace System; public value struct CSquare { double Side; }; int main() { CSquare *square = new CSquare; square->Side = 24.48; Console::WriteLine("Square Characteristics"); Console::Write("Side: "); Console::WriteLine(square->Side); Console::WriteLine(); return 0; }
Practical Learning: Allocating Memory on the Native Heap |
using namespace System; public value class CProperty { public: __wchar_t TypeOfHome; int Bedrooms; float Bathrooms; Byte Stories; int YearBuilt; double Value; }; int main() { CProperty *condominium = new CProperty; condominium->YearBuilt = 2000; condominium->Bathrooms = 1.0f; condominium->Stories = 1; condominium->Value = 224825; condominium->Bedrooms = 1; condominium->TypeOfHome = L'C'; Console::WriteLine("=//= Altair Realty =//="); Console::WriteLine("-=- Properties Inventory -=-"); Console::Write("Type of Home: "); Console::WriteLine(condominium->TypeOfHome); Console::Write("Number of Bedrooms: "); Console::WriteLine(condominium->Bedrooms); Console::Write("Number of Bathrooms: "); Console::WriteLine(condominium->Bathrooms); 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; }
After using a variable that was declared using the new operator, you can reclaim the memory space it was using. This is done with the delete operator.
Here is an example:
using namespace System; public value struct CSquare { double Side; }; int main() { CSquare *square = new CSquare; square->Side = 24.48; Console::WriteLine("Square Characteristics"); Console::Write("Side: "); Console::WriteLine(square->Side); delete square; Console::WriteLine(); return 0; }
Practical Learning: De-Allocating Memory from the Native Heap |
using namespace System; public value class CProperty { public: __wchar_t TypeOfHome; int Bedrooms; float Bathrooms; Byte Stories; int YearBuilt; double Value; }; int main() { CProperty *condominium = new CProperty; . . . delete condominium; Console::WriteLine(); return 0; }
Declaring a Pointer to Pointer |
In Lesson 3, we saw how to declare a pointer to a pointer to a primitive type. In the same way, you can declare a pointer to a pointer to a class. Here is an example: using namespace System; public value struct CSquare { double Side; }; int main() { CSquare **square; Console::WriteLine(); return 0; } |
One way you can use such a variable is to initialize it with a reference to a pointer of the same type. This could be done as follows:
using namespace System; public value struct CSquare { double Side; }; int main() { CSquare *sqr = new CSquare; CSquare **square; square = &sqr; Console::WriteLine(); return 0; }
Remember that, for a double-pointer to a class, such as **square, the expression *square is a pointer to the class. This means that you can use the single asterisk to access the (public) members of the object. To do this, you can use the arrow -> operator. Because the -> operator has higher precedence than the asterisk, you must isolate *square by including it in parentheses. This would be done as follows:
using namespace System; public value struct CSquare { double Side; }; int main() { CSquare *sqr = new CSquare; CSquare **square; square = &sqr; (*square)->Side = 24.48; Console::WriteLine("Square Characteristics"); Console::Write("Side: "); Console::WriteLine((*square)->Side); delete sqr; Console::WriteLine(); return 0; }
You may remember that the name of a pointer variable preceded with the asterisk represents the value held by the variable. When applied to a class, for a single pointer, the name preceded with * is the value of the variable. For a double-pointer, the name preceded with two asterisks is the value held by the pointer to pointer. This allows you to access the members of the class using the period operator. The above program can be written as:
using namespace System; public value struct CSquare { double Side; }; int main() { CSquare *sqr = new CSquare; CSquare **square; square = &sqr; (**square).Side = 24.48; Console::WriteLine("Square Characteristics"); Console::Write("Side: "); Console::WriteLine((*square)->Side); delete sqr; Console::WriteLine(); return 0; }
Just as demonstrated in Lesson 3, after initializing a pointer, if you change the value of the variable it points to, the pointer would be updated. If the variable was declared as a pointer to pointer and if you change either the pointer it points to, the pointer to pointer would be updated.
Introduction |
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:
Computer Memory | |
using namespace System; int main() { Console::WriteLine("=//= Altair Realty =//="); Console::WriteLine("-=- Properties Inventory -=-"); return 0; }
#pragma once namespace RealEstate { public value class CProperty { public: long PropertyNumber; __wchar_t PropertyType; unsigned Stories; unsigned Bedrooms; float Bathrooms; unsigned YearBuilt; double PropertyValue; }; }
In our introduction to pointers, we saw that a reference was a technique of declaring an additional variable that references a primary variable. This was viewed as creating a variable that simply referred to another previously declared variable. Such a variable declared as a reference is stored in the stack. The compiler is then in charge of “cleaning” it, that is, in charge of reclaiming its memory when the reference is not used anymore. C++/CLI allows you to declare a reference that, if you want, would be stored in the CLR heap so the garbage collector would be in charge of managing it. Such a variable is called a tracking reference. As done for a primitive type, you can also create a tracking reference of a class. To start, declare and initialize a variable of the class. Once the referenced variable is ready, to create a tracking reference, declare the variable but type the % operator between the class name and the name of the variable. To indicate the variable it is referencing, assign it the variable that was previously defined. Here is an example: |
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; }
Practical Learning: Creating a Handle |
#include "Property.h" using namespace System; using namespace RealEstate; int main() { CProperty ^ house; Console::WriteLine("=//= Altair Realty =//="); Console::WriteLine("-=- Properties Inventory -=-"); 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++ 2008, 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.
Practical Learning: Creating a Reference Class |
#pragma once namespace RealEstate { public ref class CProperty { public: long PropertyNumber; __wchar_t PropertyType; unsigned Stories; unsigned Bedrooms; float Bathrooms; unsigned YearBuilt; double PropertyValue; }; }
Accessing the Members of a Handle |
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.
Practical Learning: Initializing a Handle |
#include "Property.h" using namespace System; using namespace RealEstate; int main() { CProperty ^ house = gcnew CProperty; house->PropertyNumber = 792749; house->PropertyType = 'S'; house->Stories = 2; house->Bedrooms = 4; house->Bathrooms = 2.0f; house->YearBuilt = 1956; house->PropertyValue = 412650; Console::WriteLine("=//= Altair Realty =//="); Console::WriteLine("-=- Properties Inventory -=-"); Console::Write("Property #: "); Console::WriteLine(house->PropertyNumber); Console::Write("Type: "); Console::WriteLine(house->PropertyType); Console::Write("Bedrooms: "); Console::WriteLine(house->Bedrooms); Console::Write("Bathrooms: "); Console::WriteLine(house->Bathrooms); Console::Write("Stories: "); Console::WriteLine(house->Stories); Console::Write("Year Built: "); Console::WriteLine(house->YearBuilt); Console::Write("Market Value: "); Console::WriteLine(house->PropertyValue); return 0; }
=//= Altair Realty =//= -=- Properties Inventory -=- Property #: 792749 Type: S Bedrooms: 4 Bathrooms: 2 Stories: 2 Year Built: 1956 Market Value: 412650 Press any key to continue . . .
|
|||
Previous | Copyright © 2006-2025, FunctionX | Monday 14 October 2024, 10:12 | Next |
|