Lesson Home

Built-In Classes

The Ancestry of Managed Classes

 

Introduction

While the C++ language was created as a general purpose language, Managed C++ was meant to serve as the grounding rock for Microsoft Visual C++ .NET programming environment, with the ability to fully support the Microsoft .NET Framework. Since the Microsoft .NET Framework is primarily a library, Managed C++ relies on it heavily for the ability to create large and performing computer applications of any kind, including Windows, web, communication, database, robotics, etc. We mentioned already that Managed C++ is one of the most, if not the most, wide languages ever created, as it support a great number of libraries. We also mentioned that to create a managed class, it must use the __gc keyword.

 

The Object Class

In Managed C++, when creating a class, you have an incredible number of choices of the approach to use, including C, C++, STL, .NET Framework, ATL, etc. You can create a regular C++ application that can be used in other environments or even other operating systems. To take advantage of the Microsoft .NET Framework, you should create it as garbage collected, which is taken care of by simply starting the creation of the class with the __gc keyword. Here is an example:

Header File: school.h
#pragma once

public __gc class CSchool
{
public:
	CSchool(void);
};
Source File: school.cpp
#include "StdAfx.h"
#include ".\school.h"
#using <mscorlib.dll>

using namespace System;

CSchool::CSchool(void)
{
	Console::WriteLine(S"The CSchool constructor was called");
}

Once a class has been created as garbage collected, using the __gc keyword, it automatically inherit its foundation from an ancestor class called Object. Therefore, using __gc is equivalent to inheriting from Object:

using namespace System;

__gc class CSchool : public Object
{

};

Since the __gc keyword already implies that its class is inheriting from Object, you don't need to show that you are inhering your class from Object. In fact, since Managed C++ doesn't support multiple inheritance except when involving interfaces, you should avoid indicating that you are deriving from Object.

The strength of inheritance lies on the ability the use the parent and ancestor's properties and methods. Based on this, the Object class provides the low level functionality that any regular class would need.

The Object class is equipped with only one constructor, the default, whose syntax is:

public Object();

The Object() constructor is simply used to create an instance of a class. Based on the rules of polymorphism we reviewed, a variable declared as Object can be initialized with a __gc class. Here is an example:

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

#include "stdafx.h"
#include ".\school.h"

#using <mscorlib.dll>

using namespace System;

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

    return 0;
}

Even though the class was created as an Object variable, once initialized appropriately, when it is used, its own functionality is made available. The above program would produce:

The CSchool constructor was called
Press any key to continue

Equality of Variables

Besides it default constructor, the Object class provides its children with the ability to test the equality of two variables declared from managed classes. The method that takes care of this is called Equals and it comes in two versions. The syntax of one of the Object::Equals() methods is:

virtual bool Equals(Object* variable);

This method can be called by a variable that needs to check the equivalency of its value with the value of another variable. The virtual keyword indicates that you can override this method in any __gc class as you judge a new implementation necessary (this is one of the ways Managed C++ allows you to provide particular functionality without using operator overloading).

The other version of the Object::Equals() method is declared as follows:

static bool Equals(Object* object1, Object* object);

This version is also used to compare two managed variables. This time, since the method is static, you can use the Object::Equals() formula and pass both variables as arguments.

The Type of a Variable

The Object class provides another very valuable method, called GetType. Its syntax is:

Type *GetType();

The Object::GetType() method allows you to find out the kind of data type or the class that was used to declare a variable or pass an argument. Here is an example:

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

#include "stdafx.h"
#include ".\school.h"

#using <mscorlib.dll>

using namespace System;

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

    Console::WriteLine(school->GetType());
	
    return 0;
}

This would produce:

The CScholl constructor was called
CSchool

Press any key to continue

String a Variable

Another very important method provided by the Object class is ToString(). Its syntax is:

virtual String *ToString();

The ToString() method is used to return a String value of the variable that calls it. In some cases, it returns the current value of the variable that calls it. In some cases, it returns 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"
#include ".\school.h"

#using <mscorlib.dll>

using namespace System;

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

    Console::WriteLine(school->GetType());
    Console::WriteLine(school->ToString());
    return 0;
}

This would produce:

The CScholl constructor was called
CSchool
CSchool
Press any key to continue
 

The Copy of an Object

While the Managed C++ compiler creates or makes use of a default constructor, it also provides functionality to assign an instance of one class to another. In C++, this is taken care of by a method called the copy constructor. Unlike C++, Managed C++ doesn't encourage you to create a copy constructor.

To assign one object to another, simply use the assignment operator "=" or the parentheses. Keep in mind that, like regular variables, you can use parentheses only when declaring the variable. Here are examples of both techniques of assigning one object to another:

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

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;

public __gc class CRectangle
{
private:
    double length;
    double height;
public:
    CRectangle();
    CRectangle(double Len);
    CRectangle(double Len, double Hgt);
    CRectangle(const CRectangle &rect)
	{
		length = rect.length;
		height = rect.height;
	}

    double Perimeter();
    double Area();
    void   ShowCharacteristics();
};

CRectangle::CRectangle()
{
    length = 0.00;
    height = 0.00;
}

CRectangle::CRectangle(double Len)
{
    length = Len;
    height = 0.00;
}

CRectangle::CRectangle(double L, double H)
{
    length = L;
    height = H;
}

double CRectangle::Perimeter()
{
    return 2 * (length + height);
}

double CRectangle::Area()
{
    return length * height;
}

void CRectangle::ShowCharacteristics()
{
    Console::WriteLine("Length:    {0:F}", __box(length));
    Console::WriteLine("Width:     {0:F}", __box(height));
    Console::WriteLine("Perimeter: {0:F}", __box(Perimeter()));
    Console::WriteLine("Area:      {0:F}", __box(Area()));
}
	
int _tmain()
{
    // TODO: Please replace the sample code below with your own.
    CRectangle *seat = new CRectangle(14.68, 10.85);

    CRectangle *pieceOfPaper = new CRectangle(28.92, 22.74);

    Console::WriteLine("- A Piece of Paper -");
    pieceOfPaper->ShowCharacteristics();
    Console::WriteLine();

    CRectangle *bedroom(seat);
	
    Console::WriteLine("- Bedroom Floor -");
    bedroom->ShowCharacteristics();
    Console::WriteLine();

    CRectangle *napkin = pieceOfPaper;

    Console::WriteLine("- Napkin -");
    napkin->ShowCharacteristics();

    Console::WriteLine();
    return 0;
}

This produce:

A Piece of Paper
Length:    28.92
Width:     22.74
Perimeter: 103.32
Area:      657.64

Bedroom Floor
Length:    14.68
Width:     10.85
Perimeter: 51.06
Area:      159.28

Napkin
Length:    28.92
Width:     22.74
Perimeter: 103.32
Area:      657.64

Press any key to continue

 

Variable Destruction

 

Construction and Destruction of an Object

The C++ language features the concept of object construction and destruction. When you declare a variable, it comes alive and can be used as you see fit. When you have finished using it, the object must be destroyed so the memory it was occupying can be made available to other variables or programs. Besides the constructors that we have used when creating classes so far, a class can be equipped with another special method called a destructor. The destructor is called when a program has finished using an instance of an object. Like the default constructor, the compiler always creates a default destructor if you don't create one. In Managed C++, value types cannot have a destructor, only managed types.

Unlike the constructor, the destructor cannot be overloaded. In other words, a class can have only one destructor. Like the default constructor, a destructor also has the same name as its class. This time, the name of the destructor starts with a tilde "~". Here is an example:

Header File: school.h
#pragma once

public __gc class CSchool
{
public:
	CSchool(void);
	~CSchool(void);
};
Source File: school.cpp
#include "StdAfx.h"
#include ".\school.h"
#using <mscorlib.dll>

using namespace System;

CSchool::CSchool(void)
{
	Console::WriteLine(S"The CSchool principal is in the house");
}

CSchool::~CSchool(void)
{
	Console::WriteLine(S"The CSchool principal is leaving the building");
}

As mentioned already, when a variable declared from a class is not used anymore, the destructor is called. This can be seen in the following:

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

#include "stdafx.h"
#include ".\school.h"

#using <mscorlib.dll>

using namespace System;

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

    Console::WriteLine(S"\nThe wonderful world of Managed C++\n");
	
    return 0;
}

This would produce:

The CSchool principal is in the house

The wonderful world of Managed C++

The CSchool principal is leaving the building
Press any key to continue

You can declare pointer variables in a class. Here is one example:

Header File: school.h
#pragma once

public __gc class CSchool
{
public:
    CSchool(void);
    ~CSchool(void);

    int* SchoolCapacity;
};

If you declare a member variable of a class as a pointer, you can use one of the constructors, usually the default, to dynamically allocated memory for it using the new operator. After using such a variable, you can then delete or destroy it in the destructor, using the delete operator, to reclaim the memory it was using since the variable of the class is going to be destroyed too. Here is an example:

Source File: school.cpp
#include "StdAfx.h"
#include ".\school.h"
#using <mscorlib.dll>

using namespace System;

CSchool::CSchool(void)
    : SchoolCapacity(NULL)
{
    Console::WriteLine(S"The CSchool principal is in the house");

    SchoolCapacity = new int;
}

CSchool::~CSchool(void)
{
    delete SchoolCapacity;

    Console::WriteLine(S"The CSchool principal is leaving the building\n");
}

This can be tested as follows:

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

#include "stdafx.h"
#include ".\school.h"

#using <mscorlib.dll>

using namespace System;

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

	*school->SchoolCapacity = 6248;

	Console::Write(S"\nScholl Capacity: ");
	Console::WriteLine(*school->SchoolCapacity);
	Console::WriteLine();
	
	return 0;
}

This would produce:

The CSchool principal is in the house

Scholl Capacity: 6248

The CSchool principal is leaving the building

Press any key to continue

Consider a case where you have created a class that is derived from another class. When you dynamically call the inherited class using an instance of the base class (as done in the last example), during the closing of the program, the destructor of the child class is called first, followed by the destructor of the base class. If you dynamically declare an instance of the base class and then invoke the children of the class, you need to make sure that each destructor and the right destructor of the classes that were used is called to destroy the class. This is a safe measure to avoid a memory leak. This aspect of C++ programming is taken care of by declaring the destructor of the base class as virtual.

Whenever the destructor of the parent class is declared virtual, the destructor of an inherited class is also virtual. This ensures that, when the program closes, all of the destructors of the base class and its children that were used are called, providing a safe claim of the memory that was used.

To declare a destructor as virtual, type the virtual keyword on its left, in the body of the class.

 

The Finalizer

As mentioned already, when a variable is not needed anymore, it should be removed from memory so the space it was occupying can be made available to other variables. To manually remove such a variable, you can use 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"
#include ".\school.h"

#using <mscorlib.dll>

using namespace System;

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

	*school->SchoolCapacity = 6248;

	Console::Write(S"\nScholl Capacity: ");
	Console::WriteLine(*school->SchoolCapacity);
	Console::WriteLine();

	delete school;
	
	return 0;
}

When it comes to objects, if you create a destructor for your class, it is easy to know when the destructor would be called, as we saw in the example of our CSchool class. For this reason, a destructor is said to deterministic. As mentioned already, you don't always have to create a destructor for your class: if you don't, the compiler would create one for you. You should create a destructor (only) if you have a good reason to. In our CSchool class, we created a destructor because we wanted to delete the dynamic SchoolCapacity variable we had declared on the heap.

To automate the C++ concept of variable destruction, the Object class provides a special method to managed classes. This method is called Finalize. The Object::Finalize() method takes care of removing a variable from memory when it judges that the variable is not needed anymore. This is done internally so that you would not need to remember to destroy a variable.

When various variables are used in a program, it is hard to know when the Finalize() method would be called. Therefore, the Finalize() method is considered non-deterministic. For this reason, we will let the compiler decide when it becomes necessary.

If you don't want to rely on the Finalize() method cleaning memory, you can call the SuppressFinalize() method of the GC class.

 

 

Previous Copyright © 2004-2010 FunctionX, Inc. Next