Logo

Classes, Pointers, and the Heap

 

Value Types

 

Introduction

In Managed C++, if you are creating a small class that is made of one or more variables of the simple data types, such a class can be created as a value type. To create such a class, type the __value keyword to its left. Here is an example:

__value struct CartesianPoint
{
}

Imagine you want to write a program that deals with the Cartesian coordinate system on which you want to represent some geometric points:

A Coordinate System

To locate any point on this coordinate system, each point is represented by its measurement on the x axis and its measurement on the y axis. In other words every point is represented by two values: x and y. Such a point can be created with a CCartesianPoint name as follows:

__value struct CCartesianPoint
{
	int x;
	int y;
};

Access to Value types

Like the classes we have used so far, before using a variable of this value type, you must first declare it. Besides the techniques of declaring a variable as we reviewed earlier, you can also precede the declaration with the __value keyword:

__value struct CartesianPoint
{
	int x;
	int y;
};

int _tmain()
{
    	// TODO: Please replace the sample code below with your own.
	__value struct CartesianPoint A;

	return 0;
}

As done earlier, to declare a variable of a structure, you can omit the struct keyword. Here is an example:

using namespace System;

__value struct CCartesianPoint
{
	int x;
	int y;
};

int _tmain()
{
    	// TODO: Please replace the sample code below with your own.
	__value CCartesianPoint A;

	return 0;
}

You can also omit the __value keyword when declaring the variable:

using namespace System;

__value struct CartesianPoint
{
	int x;
	int y;
};

int _tmain()
{
    	// TODO: Please replace the sample code below with your own.
	CartesianPoint A;

	return 0;
}

In this case, the compiler would check how the class was created. In our example, since the class was created with the __value keyword, when declaring the variable, the compiler would know that the variable is a value type. When you declare a variable, we say that you have created an object; that is, the variable is also called an object, or an instance of the class. The word instance here means that the object is alive or has been made alive in your program. Such an instance, like A in the above declaration, is available for the duration of your program.

Once an object has been created or instantiated, you can access any of its members using the member access operator ".". First, type the name of the declared variable, followed by a period, followed by the name of the member you want to access. For example, to access the x member of the above StartLine variable object, you would write:

StartLine.x

 

Pointers

 

Static Memory Allocation

In the previous lesson, we saw that an area of memory is made available to you when you declare variables to store their values. The types of variables stored in this stack memory are called value types because, as we mentioned and demonstrated in Lesson 2, these variables hold their own values. As such, we have used the stack to store our variables and let the operating system manage their lifetime, deciding when to store the variables and when to get rid of them.

When you declare a variable on the stack, the memory made available is said to be statically allocated. Code written in this manner is referred to as unmanaged because you are not in charge of managing it. For this reason, there are two ways you will use C++ variables in your programs. Unmanaged code means that you declare your variables on the stack and let the operating system, rather than yourself, manage them. Managed code is the other technique we are going to introduce and that is the heart of Managed C++.

Imagine you declare an integer variable as follows:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;
using namespace std;

int _tmain()
{
    // TODO: Please replace the sample code below with your own.
    int Number = 248;

    Console::WriteLine();
    return 0;
}

When you declare a variable using static allocation, the name of the variable holds the value stored in the memory space that was provided. Whenever you need that value, you can simply call such a variable using its name as you judge it necessary. Here is an example:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;
using namespace std;

int _tmain()
{
    // TODO: Please replace the sample code below with your own.
    int Number = 248;

    Console::WriteLine(Number);
    return 0;
}

This would produce:

248
Press any key to continue

When you call a variable using this approach, a copy of the value that the variable holds is given to you but the memory that holds the variable is not accessed. This means that, whatever you do that value is up to you but you cannot directly have access to the memory that holds the value.

 

Value Types and Dynamic Memory Allocation

C++ allows another technique used to get to the value of a variable. Instead of getting the actual value of a variable, you can declare a variable whose name would only "direct" or "point" you to the address of memory that holds the value. Such a variable is called a pointer.

To declare a pointer variable, like a normal variable, you must first specify the type of data for which you are requesting memory. Then, you must precede the name of the variable with an asterisk. The syntax used is:

VariableType *VariableName;

Here is an example:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;
using namespace std;

int _tmain()
{
    // TODO: Please replace the sample code below with your own.
    int *Number;
	
    Console::WriteLine();
    return 0;
}

The above declaration tells the compiler that you want to use a variable that points to an area of memory that holds an integer value. If you don't initialize it, you would receive an error when the program compiles. Therefore, after declaring the pointer, you must initialize it before using it.

To initialize a pointer, you can use the new operator and specify the same type of data that you used to declare the pointer with. There are two rules you must also keep in mind. First, the name of the variable without the asterisk is the pointer. Based on this, you can initialize it as follows:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;
using namespace std;

int _tmain()
{
    // TODO: Please replace the sample code below with your own.
    int *Number;
	
    Number = new int;

    Console::WriteLine();
    return 0;
}

You can also initialize the variable when declaring it. Here is an example:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;
using namespace std;

int _tmain()
{
    // TODO: Please replace the sample code below with your own.
    int *Number = new int;
	
    Console::WriteLine();
    return 0;
}

After initializing the pointer (there are other techniques we will use to initialize a pointer), the memory that the variable points to is made available to you but it is initially filled with an insignificant value, comparable to garbage. An example of such a value would be

-842150451
Press any key to continue

The second rule you must keep in mind is that the name of the pointer variable preceded with the asterisk represents the value the variable points to. Therefore, if you want to "put" a value in the allocated memory, in other words if you want to initialize the variable, you must assign the desired value to the pointer. Here is an example:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;
using namespace std;

int _tmain()
{
    // TODO: Please replace the sample code below with your own.
    int *Number = new int;
	
   *Number = 248;

    return 0;
}

In the same way, to access the value of that variable, you must keep the asterisk with the name of the variable:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;
using namespace std;

int _tmain()
{
    // TODO: Please replace the sample code below with your own.
    int *Number = new int;
	
    *Number = 248;

    Console::WriteLine(*Number);
    return 0;
}

The memory used when declaring a pointer is said to be dynamically allocated. After using that memory, you must reclaim it. This is done using the delete operator succeeded by the name of the variable. The formula used is:

delete VariableName;

Although you can get away by not deleting a pointer after using it, you cannot delete a pointer that was not created with the new operator. Therefore, before using delete, you must make sure that the variable you are deleting points to a dynamically allocated memory.

Various, common, and regular misuses of pointers are the origin or memory leaks. The Microsoft .NET Framework corrects this problem and eliminates the possibility of memory leaks because it defines rules you must follow when creating your classes.

 

Using Various Pointers

 

Declaring Various Variables

As mentioned with other variables, you can declare many pointers in your program as you judge them necessary. When doing this, remember to precede the name of each pointer with an asterisk. If you don't, any name of a variable not preceded with an asterisk would be treated as a normal variable. In the following example, Number1 is a pointer to an integer while Number2 is a normal variable of type int declared on the stack:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace std;
using namespace System;

int _tmain()
{
    	// TODO: Please replace the sample code below with your own.
	int *Number1, Number2;

    	Console::WriteLine();
	return 0;
}

If you wanted both Number1 and Number2 to be pointers, you could declare them as follows:

int *Number1, *Number2;

or as follows:

int *Number1;
int *Number2

As mentioned for the reference, the position of the asterisk between the data type and the name of the variable is not important as long as the compiler can distinguish the data type, the asterisk, and the name of the variable. Therefore, the following three declarations are the same:

int* Number1;
int * Number2;
int *Number3;

It is common for some books to suggest one of three positions. Based on the common code regularly generated by Visual C++, we cannot suggest one convention or another. We just point out that all of them are valid.

 

Pointers and their Values

In the previous lesson, we saw that a regular variable holds its own value and only makes available a copy of that value if another variable needs it. This was illustrated with the following program:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;

int _tmain()
{
    	// TODO: Please replace the sample code below with your own.
	int Number1 = 255;
	int Number2 = Number1;

	Console::WriteLine(Number1);
	Console::WriteLine(Number2);
	Console::WriteLine();
	
	Number2 = -48;

	Console::WriteLine(Number1);
	Console::WriteLine(Number2);
    	Console::WriteLine();
	return 0;
}

This would produce:

255
255

255
-48

Press any key to continue

In this lesson, we have mentioned that when you declare a variable as a pointer, the name of the variable only points to the area of memory in which the value is stored: the name of the variable doesn't hold the value. This means (and implies) that whenever you call the variable, you are actually accessing the address that the variable points to. Consequently, if you access the value, you would be getting the original, not the copy. In other words, since you have access to the address of the variable, if you change the value, you would actually change the original, which means the value would be permanently changed. This discussion, which is important to understand as it will be heavily used with functions, doesn't directly prove that a normal variable is different from a pointer. Therefore, we will illustrate with an example.

From what we have learned so far, imagine that you declare a variable as pointer to int as follows:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace std;
using namespace System;

int _tmain()
{
    	// TODO: Please replace the sample code below with your own.
	int* Number1 = new int;

	*Number1 = 255;

	Console::WriteLine(*Number1);

    	Console::WriteLine();
	return 0;
}

If you declare another pointer to int, you can assign it to an existing variable. Here is an example:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace std;
using namespace System;

int _tmain()
{
    	// TODO: Please replace the sample code below with your own.
	int* Number1 = new int;

	*Number1 = 255;
	int *Number2 = Number1;

	Console::WriteLine(*Number1);
	Console::WriteLine(*Number2);

    	Console::WriteLine();
	return 0;
}

This would produce:

255
255

Press any key to continue

Notice that, after the assignment, both variables point to the same value. We saw earlier that when using normal variables, the value of the second variable can be changed without affecting the first variable. We will try the same change with pointers:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace std;
using namespace System;

int _tmain()
{
    	// TODO: Please replace the sample code below with your own.
	int* Number1 = new int;

	*Number1 = 255;
	int *Number2 = Number1;

	Console::WriteLine(*Number1);
	Console::WriteLine(*Number2);
	Console::WriteLine();

	*Number2 = -48;

	Console::WriteLine(*Number1);
	Console::WriteLine(*Number2);

    	Console::WriteLine();
	return 0;
}

This would produce:

255
255

-48
-48

Press any key to continue

Notice that, this time, when the value of one of the pointers changes, the value of the other variable changes too. This proves that the variables, Number1 and Number2 in this case, don't actually hold any value. They only point to the address that holds the actual value. That's why, when changing the value using one of the pointers, the change is actually performed on the address, the source, not on the variable.

 

C++ Classes and Dynamic Memory Allocation

Imagine you create a class as follows:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;
using namespace std;

class CCar
{
public:
	string Make;
	bool   HasAirCondition;
	string Model;
	long   Mileage;
	int    NumberOfDoors;
	Char   FloorMapType;
};

int _tmain()
{
    // TODO: Please replace the sample code below with your own.

    Console::WriteLine();
    return 0;
}

To declare a pointer variable of this class, you can the new operator, as we saw already for a regular data type. Here is an example:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;
using namespace std;

class CCar
{
public:
	string Make;
	bool   HasAirCondition;
	string Model;
	long   Mileage;
	int    NumberOfDoors;
	Char   FloorMapType;
};

int _tmain()
{
    // TODO: Please replace the sample code below with your own.
    CCar *Vehicle = new CCar;

    Console::WriteLine();
    return 0;
}

With a variable declared as a pointer, to access any of its members, you use the -> operator instead of the period operator. For example, to assign a name to the Make member variable of the Vehicle pointer, you would write:

Vehicle->Make = "Lincoln";

You can use this -> operator to access the member variable any time, either to change its value or to request it. This is illustrated in the following program:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;
using namespace std;

class CCar
{
public:
	string Make;
	string Model;
	int    NumberOfDoors;
	long   Mileage;
	bool   HasAirCondition;
	Char   FloorMatType;
};

int _tmain()
{
    	// TODO: Please replace the sample code below with your own.
	CCar *vehicle = new CCar;

	vehicle->Make = "Lincoln";
	vehicle->Model = "TownCar";
	vehicle->NumberOfDoors = 2;
	vehicle->Mileage = 24882;
	vehicle->HasAirCondition = true;
	vehicle->FloorMatType = 'D';

	cout << "Vehicle Information";
	cout << "\nMake:      " << vehicle->Make;
	cout << "\nModel:     " << vehicle->Model;
	cout << "\nDoors:     ";
	Console::WriteLine(vehicle->NumberOfDoors);
	cout << "Mileage:   ";
	Console::Write(vehicle->Mileage);
	cout << " miles\n";
	cout << "Has A/C:   ";
	Console::WriteLine(vehicle->HasAirCondition);
	cout << "Floor Mat: ";
	Console::WriteLine(vehicle->FloorMatType);

    	Console::WriteLine();
	return 0;
}

This would produce:

Vehicle Information
Make:      Lincoln
Model:     TownCar
Doors:     2
Mileage:   24882 miles
Has A/C:   True
Floor Mat: D

Press any key to continue

As mentioned earlier, after using a pointer variable, you must remember to get rid of it and reclaim the memory space it was using, which is taken care of using the delete operator. Here is an example:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;
using namespace std;

class CCar
{
// . . .
};

int _tmain()
{
    	// TODO: Please replace the sample code below with your own.
	CCar *Vehicle = new CCar;

	//  . . .

	delete Vehicle;

    	Console::WriteLine();
	return 0;
}

The Heap

 

Garbage Collection

In the previous lesson, we saw that the operating is in charge of setting aside an area of memory that your application can use as needed. When your Managed C++ application comes, the Microsoft .NET Framework sets aside another area of memory that it would manage for you. This area of memory is called the Managed Heap. To use this area, you must request an amount you need, using a pointer as we saw above. The particularity of this memory is that there is an "engine" in charge of managing it. This engine is called the Garbage Collector. After requesting memory, you can use it as you see fit. While your program is running or after using it, you don't need to remember to delete an object. The garbage collector decides or knows when to clean it; that is, when to remove the object from memory.

To use the garbage collector, there are rules you must follow. When creating a class, you must precede the class keyword with the __gc keyword. Here is an example:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;

__gc class CRectangle
{
public:
    Double length;
    Double height;
};

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

The __gc keyword indicates that the garbage collector will be in charge of deleting the variable when not needed anymore. When declaring a variable of a __gc class type, you can use the __gc keyword after typing the name of the class. Here is an example:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;

__gc class CRectangle
{
public:
	Double length;
	Double height;
};

int _tmain()
{
    // TODO: Please replace the sample code below with your own.
    CRectangle __gc *Rect = new CRectangle;

    Console::WriteLine();
    return 0;
}

You can also use the __gc keyword just before new. You can omit the __gc keyword or you can use it on both sides of the assignment operator. Here four valid and equivalent pointer declarations of the same class:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;

__gc class CRectangle
{
public:
	Double length;
	Double height;
};

int _tmain()
{
    	// TODO: Please replace the sample code below with your own.
	CRectangle      *Rect1 =      new CRectangle;
	CRectangle __gc *Rect2 =      new CRectangle;
	CRectangle      *Rect3 = __gc new CRectangle;
	CRectangle __gc *Rect4 = __gc new CRectangle;

    	Console::WriteLine();
	return 0;
}

Any of these declarations is valid because, when the compiler comes to the declaration, it would check how the class was created. If the class was created with the __gc keyword, the compiler would consider that the variable will be garbage collected, whether you include the __gc keyword during the declaration or not. Besides other rules we will review with such topics as constructors, you must make sure that the variable is declared with the new keyword.

After declaring the variable, you can access any of its members using the -> operator, exactly as done with the earlier pointers. Here is an example:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace std;
using namespace System;

__gc class CRectangle
{
public:
	Double length;
	Double height;
};

int _tmain()
{
    	// TODO: Please replace the sample code below with your own.
	CRectangle      *Rect1 =      new CRectangle;
	CRectangle __gc *Rect2 =      new CRectangle;
	CRectangle      *Rect3 = __gc new CRectangle;
	CRectangle __gc *Rect4 = __gc new CRectangle;

	Rect1->length = 25.55;
	Rect3->height = 20.75;

	cout << "Rectangle Characteristics";
	cout << "\nLength: ";
    	Console::Write(Rect1->length);
	cout << "\nHeight: ";
    	Console::Write(Rect1->height);

    	Console::WriteLine();
	return 0;
}

As we have seen so far, both a structure and a class can be created as a value type using the __value keyword:
 

#using <mscorlib.dll>
using namespace System;

__value struct CRectangle
{
public:
    double Length;
    double height;
};

int main()
{
    CRectangle Rect;

    Rect.length = 20.25;
    Rect.height= 24.52;

    return 0;
}
#using <mscorlib.dll>
using namespace System;

__value class CRectangle
{
public:
    double Length;
    double height;
};

int main()
{
    CRectangle Rect;

    Rect.length = 20.25;
    Rect.height= 24.52;

    return 0;
}

In the same way, both a structure and a class can be created as garbage collector items using the __gc keyword:

#using <mscorlib.dll>
using namespace System;

__gc struct CRectangle
{
public:
    double length;
    double height;
};

int main()
{
    CRectangle *Rect = new CRectangle;

    Rect->length = 20.25;
    Rect->height= 24.52;

    return 0;
}
#using <mscorlib.dll>
using namespace System;

__gc class CRectangle
{
public:
    double length;
    double height;
};

int main()
{
    CRectangle *Rect = new CRectangle;

    Rect->length = 20.25;
    Rect->height= 24.52;

    return 0;
}

Unless specified otherwise, keep in mind that the default access is the difference between the structure and the class. In this e-book, we will use both the struct and the class keywords unpredictably to create a class. By pure habit, programmers usually use the struct for __value types and the class for __gc or formal classes.

Metadata

It is undeniable that Managed C++' goals are to make C++ more effective. Besides improving the language, it also added features that could appear complicated at time. For example, in C++, when a program is compiled, it is translated into a language the computer can understand: the machine language. In order for programs written in other languages such as Visual Basic or C# to access the code written in Managed C++, when a program is executed, the compiler first translates it in a language called intermediate language, which can be read by the MSIL compiler. This compiler creates a file that we traditionally called a library or a dynamic library. In the Managed C++ world, this library is called an assembly. This assembly allows other applications that follow the Microsoft .NET Framework's rules to share code. This is why there are various rules you also must follow when creating your classes and even when writing your code.

An assembly contains tags of codes also called metadata. Some of these tags describe how the code can be accessed by other programs or applications. If you create a program and you want its code to be accessed by other files that include #using tags, you should start the creation of the class with the public keyword. Such a class can be started as follows:

// This is the main project file for VC++ application project 
// generated using an Application Wizard.

#include "stdafx.h"

#using <mscorlib.dll>

using namespace std;
using namespace System;

public __gc class CRectangle
{
public:
    double Length;
    double Height;
};

int _tmain()
{
    // TODO: Please replace the sample code below with your own.
    CRectangle *rect = new CRectangle;

    rect->length = 20.25;
    rect->height = 24.52;

    cout << "Rectangle Characteristics";
    cout << "\nLength: ";
    Console::Write(rect->length);
    cout << "\nHeight: ";
    Console::Write(rect->height);

    Console::WriteLine("\n");
    return 0;
}

This would produce:

Rectangle Characteristics
Length: 20.25
Height: 24.52

Press any key to continue

 

 


Previous Copyright © 2004-2010 FunctionX, Inc. Next