Tuesday, October 23, 2007

Comparing Object-Oriented Features of Delphi, C++, C# and Jav

Source: http://www.derangedcoder.net

Contents


Introduction

This document attempts to compare some of the object oriented features available in the Delphi, C++, C# and Java programming languages. Introductory paragraphs to each section to explain the concept being compared in the section are available, but are designed to be brief, introductory discussions only, not comprehensive definitions. The primary purpose of these introductory paragraphs is to highlight the differences between the implementations of the concept in the languages being compared, not to serve as tutorials explaining the concept being compared.

Unless otherwise noted, all source samples listed below were tested and successfully compiled with the following tools:

  • Delphi: Borland® Delphi™ 7 Enterprise Edition
  • C++: Borland® C++Builder Compiler and Command Line Tools free download (bcc32 v5.5)
  • C#: Microsoft® .NET Framework v1.1 SDK
  • Java: Java™ 2 SDK, Standard Edition 1.4.2_04

Objects and Classes

A class is a structure that contains data (also known as fields or attributes) and instructions for manipulating that data (also referred to as methods or behavior). Taken as a whole, a class should model a real world physical or conceptual object. An object is a dynamic instance of a class. That is, a class provides a blueprint for an object.

Here are examples for how classes are defined:

Delphi:

    
Unit1.pas:
unit Unit1;

interface

type
TTest1 = class
public
constructor Create;
destructor Destroy; override;
procedure Method1;
end;

implementation

{ TTest1 }

constructor TTest1.Create;
begin
inherited Create;
//
end;

destructor TTest1.Destroy;
begin
//
inherited Destroy;
end;

procedure TTest1.Method1;
begin
//
end;

end.


C++:

     
test1.h:
#ifndef Test1_H
#define Test1_H

class Test1 {
public:
Test1(void);
~Test1(void);
void method1(void);
};
#endif // Test1_H


test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif

Test1::Test1(void)
{
//
}

Test1::~Test1(void)
{
//
}

void Test1::method1(void)
{
//
}


C#:

    
test1.cs:
public class Test1
{
public Test1() {
//
}

public void Method1() {
//
}
}


Java:

    
Test1.java:
public class Test1 {
public Test1() {
//
}

public void method1() {
//
}
}


Encapsulation

Encapsulation refers to the hiding of implementation details such that the only way to interact with an object is through a well-defined interface. This is commonly done by defining access levels.

Delphi has 4 commonly used access levels for members: private, protected, public and published. Members marked as private are accessible to the members of the class or any method defined in the same unit's implementation section. Members marked as protected are accessible by any method of the class or its descendant units; the descendant units can be defined in different units. Public members are accessible to all. Published members behave similar to public members, except the compiler generates extra RunTime Type Information (RTTI). The default access level is public.

The access to a class is determined by which section of the unit the class is defined in: classes defined in the interface section are accessible to all, classes defined in the implementation section are only accessible by methods defined in the same unit.

In addition to these, Delphi.NET introduced the access levels of "strict private" and "strict protected" which essentially behave the same as the classic private and protected access levels except unit scope has been removed.

Here is a Delphi code example:

    
Unit1.pas:
unit Unit1;

interface

type
TTest1 = class
private
FPrivateField: Integer;
protected
procedure ProtectedMethod;
public
constructor Create;
destructor Destroy; override;
procedure PublicMethod;
published
procedure PublishedMethod;
end;

implementation

{ TTest1 }

constructor TTest1.Create;
begin
inherited Create;
//
end;

destructor TTest1.Destroy;
begin
//
inherited Destroy;
end;

procedure TTest1.ProtectedMethod;
begin
//
end;

procedure TTest1.PublicMethod;
begin
//
end;

procedure TTest1.PublishedMethod;
begin
//
end;

end.


C++ defines 3 access levels: private, protected and public. The following definitions are taken from Bjarne Stroustrup's C++ Glossary:

  • private base: a base class declared private in a derived class, so that the base's public members are accessible only from that derived class.
  • private member: a member accessible only from its own class.
  • protected base: a base class declared protected in a derived class, so that the base's public and protected members are accessible only in that derived class and classes derived from that.
  • protected member: a member accessible only from classes derived from its class.
  • public base: a base class declared public in a derived class, so that the base's public members are accessible to the users of that derived class.
  • public member: a member accessible to all users of a class.

In addition to these access levels, C++ also has the notion of a friend class and friend functions:

  • friend: a function or class explicitly granted access to members of a class by that class.
  • friend function: a function declared as friend in a class so that it has the same access as the class' members without having to be within the scope of the class.

Here is a C++ code example:

     
test1.h:
#ifndef Test1_H
#define Test1_H

class Test1 {
private:
int privateField;
protected:
void protectedMethod(void);
public:
Test1(void);
~Test1(void);
void publicMethod(void);
};
#endif // Test1_H


test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif

Test1::Test1(void)
{
//
}

Test1::~Test1(void)
{
//
}

void Test1::protectedMethod(void)
{
//
}

void Test1::publicMethod(void)
{
//
}


C# defines 5 access modifiers for class members: private, protected, internal, protected internal and public. Of these access modifiers, private is the default and members marked as such are accessible only to the class in which it is defined. A member declared as protected is accessible to the class wherein it is declared and also in any classes which derive from that class. The internal access modifier specifies that the member is accessible to any classes defined in the same assembly. A member declared as protected internal will be accessible by any derived class as well as any classes defined in the same assembly. A public member is accessible to all.

The same access modifiers are available for use on classes as well, though private, protected and protected internal are only applicable to nested classes. The default access level for a non-nested class if no access modifier is specified is internal. The default access level for a nested class if no access modifier is specified is private.

Here is a C# code example:

    
test1.cs:
public class Test1
{
public Test1() {
//
}

private int _privateField;

protected void ProtectedMethod() {
//
}

internal void InternalMethod() {
//
}

protected internal void ProtectedInternalMethod() {
//
}
}


Java defines 4 access levels for class members. The public modifier specifies that the member is visible to all. The protected modifier specifies that the member is visible to subclasses and to code in the same package. The private modifier specifies that the member is visible only to code in the same class. If none of these modifiers are used, then the default access level is assumed; that is, the member is visible to all code in the package.

Java also defines the following access modifiers for classes: public, protected and private. Of these, protected and private are only applicable for nested classes. Declaring a class as public makes the class accessible to all code. If no access modifier is specified, the default access level of package level access is assumed.

Here is a Java code example:

    
Test1.java:
public class Test1 {
public Test1() {
//
}

private int privateField;

protected void protectedMethod() {
//
}

void packageMethod() {
//
}
}


Inheritance

Inheritance refers to the ability to reuse a class to create a new class with added functionality. The new class, also referred to as the descendant class, derived class or subclass, is said to inherit the functionality of the ancestor class, base class or superclass.

C++ supports multiple implementation inheritance; that is, a derived class has more than one immediate base class to inherit its implementation from. In contrast to this, Delphi, C# and Java support single implementation inheritance, wherein the descendant class or subclass can only have one direct ancestor class or superclass to inherit its implementation from. They do, however, support using multiple interface inheritance, an interface being an abstract type that defines method signatures but do not have an implementation.

Here are some code examples:

Delphi:

    
Unit1.pas:
unit Unit1;

interface

type
TBaseClass = class(TInterfacedObject)
public
constructor Create;
destructor Destroy; override;
end;

IFooInterface = interface
procedure Foo;
end;

IBarInterface = interface
function Bar: Integer;
end;

TDerivedClass = class(TBaseClass, IFooInterface, IBarInterface)
procedure Foo;
function Bar: Integer;
end;

implementation

{ TBaseClass }

constructor TBaseClass.Create;
begin
inherited Create;
//
end;

destructor TBaseClass.Destroy;
begin
//
inherited Destroy;
end;

{ TDerivedClass }

procedure TDerivedClass.Foo;
begin
//
end;

function TDerivedClass.Bar: Integer;
begin
Result := 0;
end;

end.


C++:

     
test1.h:
#ifndef Test1_H
#define Test1_H

class Base1 {
public:
void foo(void);
};


class Base2 {
public:
int bar(void);
};


class DerivedClass : public Base1, public Base2 {
public:
DerivedClass(void);
~DerivedClass(void);
};
#endif // Test1_H


test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif

void Base1::foo(void)
{
//
}


int Base2::bar(void)
{
return 0;
}


DerivedClass::DerivedClass(void)
{
//
}

DerivedClass::~DerivedClass(void)
{
//
}


C#:

    
test1.cs:
public class BaseClass
{
public BaseClass() {
//
}
}


public interface IFooInterface
{
void Foo();
}


public interface IBarInterface
{
int Bar();
}


public class DerivedClass : BaseClass, IFooInterface, IBarInterface
{
public void Foo() {
//
}

public int Bar() {
return 0;
}
}


Java:

    
Test1.java:
class BaseClass {
public BaseClass() {
//
}
}


interface FooInterface {
void foo();
}


interface BarInterface {
int bar();
}


class DerivedClass extends BaseClass implements FooInterface, BarInterface {
public void foo() {
//
}

public int bar() {
return 0;
}
}


Virtual Methods

Virtual methods allow a method call, at run-time, to be directed to the appropriate code, appropriate for the type of the object instance used to make the call. In essence, the method is bound at run-time instead of at compile-time. The method is declared as virtual in the base class and then overridden in the derived class. Virtual methods are an important part of polymorphism since the same method call can produce results appropriate to the object instance used to make the call.

Both Delphi and C# require the overriding code to be explicitly declared as an override. In C#, the new keyword must be used in derived classes to define a method in the derived class that hides the base class method. In Java, all methods are virtual by default unless the method is marked as final. Also, final methods in Java cannot be hidden.

Here is some example code showing the use of virtual methods:

Delphi:

     
Unit1.pas:
unit Unit1;

interface

type
TBase = class
public
function NonVirtualMethod: string;
function VirtualMethod: string; virtual;
end;

TDerived = class(TBase)
public
function NonVirtualMethod: string;
function VirtualMethod: string; override;
end;

implementation

{ TBase }

function TBase.NonVirtualMethod: string;
begin
Result := 'TBase.NonVirtualMethod';
end;

function TBase.VirtualMethod: string;
begin
Result := 'TBase.VirtualMethod';
end;

{ TDerived }

function TDerived.NonVirtualMethod: string;
begin
Result := 'TDerived.NonVirtualMethod';
end;

function TDerived.VirtualMethod: string;
begin
Result := 'TDerived.VirtualMethod';
end;

end.


Project1.dpr:
program Project1;

{$APPTYPE CONSOLE}

uses
SysUtils,
Unit1 in 'Unit1.pas';

var
Foo, Bar: TBase;

begin
Foo := TBase.Create;
Bar := TDerived.Create;
try
WriteLn(Foo.NonVirtualMethod);
WriteLn(Foo.VirtualMethod);
WriteLn(Bar.NonVirtualMethod);
WriteLn(Bar.VirtualMethod);
finally
Bar.Free;
Foo.Free;
end;
end.

This should produce the following output:

[c:\borland\delphi7\projects]Project1.exe
TBase.NonVirtualMethod
TBase.VirtualMethod
TBase.NonVirtualMethod
TDerived.VirtualMethod

[c:\borland\delphi7\projects]

C++:

     
test1.h:
#ifndef Test1_H
#define Test1_H

#include

using namespace std;

class Base {
public:
string nonVirtualMethod(void);
virtual string virtualMethod(void);
};


class Derived : public Base {
public:
string nonVirtualMethod(void);
virtual string virtualMethod(void);
};
#endif // Test1_H


test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
#include

string Base::nonVirtualMethod(void)
{
string temp = "Base::nonVirtualMethod";
return temp;
}

string Base::virtualMethod(void)
{
string temp = "Base::virtualMethod";
return temp;
}


string Derived::nonVirtualMethod(void)
{
string temp = "Derived::nonVirtualMethod";
return temp;
}

string Derived::virtualMethod(void)
{
string temp = "Derived::virtualMethod";
return temp;
}


main.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
#include
#include

using namespace std;

int main()
{
auto_ptr foo(new Base);
auto_ptr bar(new Derived);
cout <<>nonVirtualMethod() << endl;
cout <<>virtualMethod() << endl;
cout <<>nonVirtualMethod() << endl;
cout <<>virtualMethod() << endl;
}


makefile:
# Macros

TOOLSROOT=C:\Borland\BCC55
INCLUDEDIR=$(TOOLSROOT)\Include
LIBDIR=$(TOOLSROOT)\Lib;$(TOOLSROOT)\Lib\PSDK
COMPILER=$(TOOLSROOT)\bin\bcc32.exe
COMPILERSWTS=-tWC -c -I$(INCLUDEDIR)
LINKER=$(TOOLSROOT)\bin\ilink32.exe
LINKERSWTS=-ap -Tpe -x -Gn -L$(LIBDIR)
OBJFILES=test1.obj main.obj
LIBFILES=cw32.lib import32.lib
BASEOUTPUTFILE=main
EXEFILE=$(BASEOUTPUTFILE).exe

#implicit rules
.cpp.obj:
$(COMPILER) $(COMPILERSWTS) $<

# Explicit rules

default: $(EXEFILE)

$(EXEFILE): $(OBJFILES)
$(LINKER) $(LINKERSWTS) c0x32.obj $(OBJFILES),$(EXEFILE),,$(LIBFILES),,

clean:
del $(OBJFILES)
del $(EXEFILE)
del $(BASEOUTPUTFILE).tds

This should produce the following output:

[c:\borland\bcc55\projects]main.exe
Base::nonVirtualMethod
Base::virtualMethod
Base::nonVirtualMethod
Derived::virtualMethod

[c:\borland\bcc55\projects]

C#:

    
test1.cs:
using System;

public class BaseClass
{
public string NonVirtualMethod() {
return "BaseClass.NonVirtualMethod";
}

public virtual string VirtualMethod() {
return "BaseClass.VirtualMethod";
}
}


public class DerivedClass : BaseClass
{
new public string NonVirtualMethod() {
return "DerivedClass.NonVirtualMethod";
}

public override string VirtualMethod() {
return "DerivedClass.VirtualMethod";
}
}


public class MainClass
{
public static void Main() {
BaseClass foo = new BaseClass();
BaseClass bar = new DerivedClass();
Console.WriteLine(foo.NonVirtualMethod());
Console.WriteLine(foo.VirtualMethod());
Console.WriteLine(bar.NonVirtualMethod());
Console.WriteLine(bar.VirtualMethod());
}
}

This should produce the following output:

[d:\source\csharp\code]test1.exe
BaseClass.NonVirtualMethod
BaseClass.VirtualMethod
BaseClass.NonVirtualMethod
DerivedClass.VirtualMethod

[d:\source\csharp\code]

Java:

    
Test1.java:
class BaseClass {
public final String finalMethod() {
return "BaseClass.finalMethod";
}

public String virtualMethod() {
return "BaseClass.virtualMethod";
}
}


class DerivedClass extends BaseClass {
public String virtualMethod() {
return "DerivedClass.virtualMethod";
}
}


public class Test1 {
public static void main(String args[]) {
BaseClass foo = new BaseClass();
BaseClass bar = new DerivedClass();
System.out.println(foo.finalMethod());
System.out.println(foo.virtualMethod());
System.out.println(bar.finalMethod());
System.out.println(bar.virtualMethod());
}
}

This should produce the following output:

[d:\source\java\code]java.exe Test1
BaseClass.finalMethod
BaseClass.virtualMethod
BaseClass.finalMethod
DerivedClass.virtualMethod

[d:\source\java\code]

Constructors And Destructors / Finalizers

Constructors are special methods used to construct an instance of a class. They normally contain initialization code e.g. code for setting default values for data members, allocating resources, etc.. The objective of constructors is to allocate memory for the object and to initialize the object to a valid state before any processing is done.

Destructors serve the opposite purpose of constructors. While constructors take care of allocating memory and other resources that the object requires during its lifetime, the destructor's purpose is to ensure that the resources are properly de-allocated as the object is being destroyed, and the memory being used by the object is freed. In Delphi and C++ (i.e. unmanaged environments) the programmer is responsible for ensuring that an object's destructor is called once the object is no longer needed.

Finalizers are somewhat similar to destructors but (1) they are non-deterministic, (2) they are there to release unmanaged resources only. That is, in a managed environment (e.g. Java Virtual Machine, .NET Common Language Runtime) finalizers are called by the managed environment, not by the programmer. Hence, there is no way to determine at which point in time or in which order a finalizer will be called. Also, memory, which is considered a managed resource, is not freed by the finalizer, but by the garbage collector. Since most classes written will only be manipulating objects in memory, most objects in a managed environment will not need a finalizer.

In Delphi, constructors are defined using the constructor keyword and destructors are defined using the destructor keyword. Delphi, by convention, names its constructor Create and its destructor Destroy, but these names are not requirements. Also, Delphi constructors can be made virtual and the default destructor Destroy is virtual. To invoke a constructor in Delphi, the convention ClassName.ConstructorName is used e.g. if a class named TMyClass has a constructor Create then constructing an instance is done using " := TMyClass.Create;" To invoke the destructor, call the Free method, not the Destroy method e.g. ".Free".

In C++, C# and Java, a constructor is defined by creating a method with the same name as the class. C# and Java require the use of the 'new' keyword to invoke the constructor. For example, constructing an instance of MyClass is done using " = new MyClass()". C++ requires the use of the 'new' keyword if the object is to be allocated on the heap, otherwise simply declaring a variable of the same type as the class with invoke the constructor and create the object on the stack. In C++, C# and Java constructors must be named the same as the name of the class.

Constructors in Delphi do not implicitly call the contructors for the base class. Instead, the programmer must make an explicit call. In C++, C# and Java, a constructor will implicitly call the constructors for its base class(es).

Destructors in C# follow the same convention as C++, that is, the destructor must be named the same as the name of the class, but preceded by a tilde (~). (This syntax often misleads developers with a C++ background to expect a C# destructor to behave like a C++ destructor (i.e. it can be called in a deterministic fashion) when in fact it behaves like a finalizer.) To implement a finalizer in Java, the developer needs only to override the finalize() method of java.lang.Object.

C++ objects allocated on the stack will automatically have their destructor called when the object goes out of scope; objects explicitly allocated in the heap using the 'new' keyword must have its destructor invoked using the 'delete' keyword. While both C# destructors and Java finalizers are non-deterministic C# destructors are guaranteed to be called by the .NET CLR (except in cases of an ill-behaved application e.g. one that crashes, forces the finalizer thread into an infinite loop, etc.) and when they are called, will in turn implicitly call the other destructors in the inheritance chain, from most derived class to least derived class. Such is not the case with Java finalizers; finalizers must explicitly invoke the finalize() method of its superclass and they are not guaranteed to be invoked.

Delphi, C++, C# and Java all provide default constructors if none are defined in the current class. All support overloading of constructors.

Delphi:

     
Unit1.pas:
unit Unit1;

interface

type
TBase = class
constructor Create;
destructor Destroy; override;
end;

TDerived = class(TBase)
constructor Create;
destructor Destroy; override;
end;

TMoreDerived = class(TDerived)
constructor Create;
destructor Destroy; override;
end;

implementation

{ TBase }

constructor TBase.Create;
begin
WriteLn('In TBase.Create');
inherited;
end;

destructor TBase.Destroy;
begin
WriteLn('In TBase.Destroy');
inherited;
end;

{ TDerived }

constructor TDerived.Create;
begin
WriteLn('In TDerived.Create');
inherited;
end;

destructor TDerived.Destroy;
begin
WriteLn('In TDerived.Destroy');
inherited;
end;

{ TMoreDerived }

constructor TMoreDerived.Create;
begin
WriteLn('In TMoreDerived.Create');
inherited;
end;

destructor TMoreDerived.Destroy;
begin
WriteLn('In TMoreDerived.Destroy');
inherited;
end;

end.


Project1.dpr:
program Project1;

{$APPTYPE CONSOLE}

uses
SysUtils,
Unit1 in 'Unit1.pas';

var
Foo: TBase;

begin
Foo := TBase.Create;
Foo.Free;
WriteLn;

Foo := TDerived.Create;
Foo.Free;
WriteLn;

Foo := TMoreDerived.Create;
Foo.Free;
end.

This should produce the following output:

[c:\borland\delphi7\projects]Project1.exe
In TBase.Create
In TBase.Destroy

In TDerived.Create
In TBase.Create
In TDerived.Destroy
In TBase.Destroy

In TMoreDerived.Create
In TDerived.Create
In TBase.Create
In TMoreDerived.Destroy
In TDerived.Destroy
In TBase.Destroy

[c:\borland\delphi7\projects]

C++:

        
test1.h:
#ifndef Test1_H
#define Test1_H

class Base1 {
public:
Base1(void);
~Base1(void);
};


class Base2 {
public:
Base2(void);
~Base2(void);
};


class Derived : public Base1, public Base2 {
public:
Derived(void);
~Derived(void);
};
#endif // Test1_H


test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif

#include

using std::cout;
using std::endl;

Base1::Base1(void)
{
cout << "In Base1::Base1(void)" << endl;
}

Base1::~Base1(void)
{
cout << "In Base1::~Base1(void)" << endl;
}


Base2::Base2(void)
{
cout << "In Base2::Base2(void)" << endl;
}

Base2::~Base2(void)
{
cout << "In Base2::~Base2(void)" << endl;
}


Derived::Derived(void)
{
cout << "In Derived::Derived(void)" << endl;
}

Derived::~Derived(void)
{
cout << "In Derived::~Derived(void)" << endl;
}


main.cpp:
#ifndef Test1_H
#include "test1.h"
#endif

#include

using std::cout;
using std::endl;

int main()
{
Base1 a;
cout << endl;

Base2 b;
cout << endl;

Derived c;
cout << endl;
}

makefile:
# Macros

TOOLSROOT=C:\Borland\BCC55
INCLUDEDIR=$(TOOLSROOT)\Include
LIBDIR=$(TOOLSROOT)\Lib;$(TOOLSROOT)\Lib\PSDK
COMPILER=$(TOOLSROOT)\bin\bcc32.exe
COMPILERSWTS=-tWC -c -I$(INCLUDEDIR)
LINKER=$(TOOLSROOT)\bin\ilink32.exe
LINKERSWTS=-ap -Tpe -x -Gn -L$(LIBDIR)
OBJFILES=test1.obj main.obj
LIBFILES=cw32.lib import32.lib
BASEOUTPUTFILE=main
EXEFILE=$(BASEOUTPUTFILE).exe

#implicit rules
.cpp.obj:
$(COMPILER) $(COMPILERSWTS) $<

# Explicit rules

default: $(EXEFILE)

$(EXEFILE): $(OBJFILES)
$(LINKER) $(LINKERSWTS) c0x32.obj $(OBJFILES),$(EXEFILE),,$(LIBFILES),,

clean:
del $(OBJFILES)
del $(EXEFILE)
del $(BASEOUTPUTFILE).tds

This should produce the following output:

[c:\borland\bcc55\projects]main.exe
In Base1::Base1(void)

In Base2::Base2(void)

In Base1::Base1(void)
In Base2::Base2(void)
In Derived::Derived(void)

In Derived::~Derived(void)
In Base2::~Base2(void)
In Base1::~Base1(void)
In Base2::~Base2(void)
In Base1::~Base1(void)

[c:\borland\bcc55\projects]

C#:

    
test1.cs:
using System;

class BaseClass
{
public BaseClass() {
Console.WriteLine("In BaseClass constructor");
}

~BaseClass() {
Console.WriteLine("In BaseClass destructor");
}
}


class DerivedClass: BaseClass
{
public DerivedClass() {
Console.WriteLine("In DerivedClass constructor");
}

~DerivedClass() {
Console.WriteLine("In DerivedClass destructor");
}
}


class MoreDerivedClass: DerivedClass
{
public MoreDerivedClass() {
Console.WriteLine("In MoreDerivedClass constructor");
}

~MoreDerivedClass() {
Console.WriteLine("In MoreDerivedClass destructor");
}
}


public class MainClass
{
public static void Main() {
BaseClass foo = new BaseClass();
Console.WriteLine();

foo = new DerivedClass();
Console.WriteLine();

foo = new MoreDerivedClass();
Console.WriteLine();
}
}

This should produce similar output (results may vary due to the non-deterministic nature of finalization):

[d:\source\csharp\code]test1.exe
In BaseClass constructor

In BaseClass constructor
In DerivedClass constructor

In BaseClass constructor
In DerivedClass constructor
In MoreDerivedClass constructor

In MoreDerivedClass destructor
In DerivedClass destructor
In BaseClass destructor
In DerivedClass destructor
In BaseClass destructor
In BaseClass destructor

[d:\source\csharp\code]

Java:

    
Test1.java:
class BaseClass {
public BaseClass() {
System.out.println("In BaseClass constructor");
}

protected void finalize() throws Throwable {
System.out.println("In BaseClass finalize()");
super.finalize();
}
}


class DerivedClass extends BaseClass {
public DerivedClass() {
System.out.println("In DerivedClass constructor");
}

protected void finalize() throws Throwable {
System.out.println("In DerivedClass finalize()");
super.finalize();
}
}


class MoreDerivedClass extends DerivedClass {
public MoreDerivedClass() {
System.out.println("In MoreDerivedClass constructor");
}

protected void finalize() throws Throwable {
System.out.println("In MoreDerivedClass finalize()");
super.finalize();
}
}


public class Test1 {
public static void main(String args[]) {
BaseClass foo = new BaseClass();
System.out.println();

foo = new DerivedClass();
System.out.println();

foo = new MoreDerivedClass();
System.out.println();
}
}

This should produce similar output (results may vary due to the non-deterministic nature of finalization):

[d:\source\java\code]java.exe Test1
In BaseClass constructor

In BaseClass constructor
In DerivedClass constructor

In BaseClass constructor
In DerivedClass constructor
In MoreDerivedClass constructor

[d:\source\java\code]

Abstract Methods and Classes

An abstract method is a method which the class does not implement. In C++ terminology, such a method is termed a pure virtual function. A class which contains one or more abstract methods is often called an abstract class although in some cases the term abstract class is reserved for classes that contains only abstract methods. Abstract classes cannot be instantiated. Classes which derive from an abstract class should override and provide implementations for the abstract methods, or should themselves be marked also as abstract. Delphi uses the abstract keyword to mark a method as abstract. C# and Java use the abstract keyword to mark a method or class as abstract. A class marked as abstract is permitted but not required to have abstract members. C++ uses the =0 notation to indicate that a virtual function is a pure virtual function.

Delphi:

    
Unit1.pas:
unit Unit1;

interface

type
TAbstractBaseClass = class
public
procedure Method1; virtual; abstract;
end;

TAbstractIntermediateClass = class(TAbstractBaseClass)
public
procedure Method1; override; abstract;
procedure Method2; virtual;
end;

TConcreteClass = class(TAbstractIntermediateClass)
public
procedure Method1; override;
end;

implementation

{ TAbstractIntermediateClass }

procedure TAbstractIntermediateClass.Method2;
begin
//
end;

{ TConcreteClass }

procedure TConcreteClass.Method1;
begin
inherited Method1;
//
end;

end.


C++:

     
test1.h:
#ifndef Test1_H
#define Test1_H

class AbstractBaseClass {
public:
virtual void method1(void)=0;
};


class AbstractIntermediateClass : public AbstractBaseClass {
public:
virtual void method1(void)=0;
virtual void method2(void);
};


class ConcreteClass : public AbstractIntermediateClass {
public:
virtual void method1(void);
};
#endif // Test1_H


test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif

void AbstractIntermediateClass::method2(void)
{
//
}


void ConcreteClass::method1(void)
{
//
}


C#:

    
test1.cs:
public abstract class AbstractBaseClass
{
public abstract void Method1();
}


public abstract class AbstractIntermediateClass : AbstractBaseClass
{
public override abstract void Method1();

public virtual void Method2() {
//
}
}


public class ConcreteClass : AbstractBaseClass
{
public override void Method1() {
//
}
}


Java:

    
Test1.java:
abstract class AbstractBaseClass {
public abstract void method1();
}


abstract class AbstractIntermediateClass extends AbstractBaseClass {
public abstract void method1();

public void method2() {
//
}
}


class ConcreteClass extends AbstractIntermediateClass {
public void method1() {
//
}
}


Interfaces

An interface is an abstract type that defines members but does not provide an implementation. Interfaces are used to define a contract; a class that implements an interface is expected to adhere to the contract provided by the interface and provide implementations for all the members defined in the interface. Delphi, C# and Java support interfaces. C++ does not though an abstract class composed purely of public pure virtual methods could serve a similar purpose.

(See the earlier section on inheritance for code examples of interfaces.)


Method Overloading

Method overloading refers to the ability to define several methods with the same name but with different signatures (parameter types, number of parameters). Delphi requires that overloaded methods be marked with the overload keyword.

Delphi:

    
Unit1.pas:
unit Unit1;

interface

type
TFoo = class
public
procedure MethodA;
end;

TBar = class(TFoo)
public
procedure MethodA(const I: Integer); overload;
procedure MethodB(const I: Integer); overload;
procedure MethodB(const I: Integer; const S: string); overload;
end;

TFooBar = class(TBar)
public
procedure MethodA(const S: string); overload;
function MethodC(const I: Integer): Integer; overload;
function MethodC(const S: string): Integer; overload;
end;

implementation

{ TFoo }

procedure TFoo.MethodA;
begin
//
end;

{ TBar }

procedure TBar.MethodB(const I: Integer);
begin
//
end;

procedure TBar.MethodB(const I: Integer; const S: string);
begin
//
end;

procedure TBar.MethodA(const I: Integer);
begin
//
end;

{ TFooBar }

procedure TFooBar.MethodA(const S: string);
begin
//
end;

function TFooBar.MethodC(const I: Integer): Integer;
begin
Result := 0;
end;

function TFooBar.MethodC(const S: string): Integer;
begin
Result := 0;
end;

end.


C++:

     
test1.h:
#ifndef Test1_H
#define Test1_H

#include

using namespace std;

class Foo {
public:
void methodA(void);
};


class Bar : public Foo {
public:
void methodA(int);
void methodB(int);
void methodB(int, string);
};


class FooBar : public Bar {
public:
void methodA(string);
int methodC(int);
int methodC(string);
};
#endif // Test1_H


test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
#include

void Foo::methodA(void)
{
//
}


void Bar::methodA(int i)
{
//
}

void Bar::methodB(int i)
{
//
}

void Bar::methodB(int i, string s)
{
//
}


void FooBar::methodA(string s)
{
//
}

int FooBar::methodC(int i)
{
return 0;
}

int FooBar::methodC(string s)
{
return 0;
}


C#:

    
test1.cs:
public class Foo
{
public void MethodA() {
//
}
}


public class Bar : Foo
{
public void MethodA(int i) {
//
}

public void MethodB(int i) {
//
}

public void MethodB(int i, string s) {
//
}
}


public class FooBar : Bar
{
public void MethodA(string s) {
//
}

public int MethodC(int i) {
return 0;
}

public int MethodC(string s) {
return 0;
}
}


Java:

    
Test1.java:
class Foo {
public void methodA() {
//
}
}


class Bar extends Foo {
public void methodA(int i) {
//
}

public void methodB(int i) {
//
}

public void methodB(int i, String s) {
//
}
}


class FooBar extends Bar {
public void methodA(String s) {
//
}

public int methodC(int i) {
return 0;
}

public int methodC(String s) {
return 0;
}
}


Operator Overloading

Operator overloading is the process of providing new implementations for built-in operators (such as '+' and '-' for example) when the operands are user-defined types such as classes. This can simplify the usage of a class and make it more intuitive.

Operator overloading is available in C++ and C# and has recently been added into Delphi.NET. It is not available in the current Win32 implementation of Delphi, nor is it available in Java.

The following examples create a simple class for manipulating complex numbers. It overloads the '+' and '-' operators.

Delphi.NET:

     
Unit1.pas:
unit Unit1;

interface

type
TComplexNumber = class
public
Real: Integer;
Imaginary: Integer;
class operator Add(const X, Y: TComplexNumber): TComplexNumber;
class operator Subtract(const X, Y: TComplexNumber): TComplexNumber;
function ToString: string; override;
constructor Create(const Real, Imaginary: Integer);
end;

implementation

uses
SysUtils;

{ TComplexNumber }

constructor TComplexNumber.Create(const Real, Imaginary: Integer);
begin
inherited Create;
Self.Real := Real;
Self.Imaginary := Imaginary;
end;

class operator TComplexNumber.Add(const X,
Y: TComplexNumber): TComplexNumber;
begin
Result := TComplexNumber.Create(X.Real + Y.Real, X.Imaginary + Y.Imaginary);
end;

class operator TComplexNumber.Subtract(const X,
Y: TComplexNumber): TComplexNumber;
begin
Result := TComplexNumber.Create(X.Real - Y.Real, X.Imaginary - Y.Imaginary);
end;

function TComplexNumber.ToString: string;
begin
if (Self.Imaginary > 0) then
Result := Format('%d + %di', [Self.Real, Self.Imaginary])
else
Result := Format('%d - %di', [Self.Real, Abs(Self.Imaginary)]);
end;

end.


Project1.dpr:
program Project1;

{$APPTYPE CONSOLE}

uses
SysUtils,
Unit1 in 'Unit1.pas';

var
A, B: TComplexNumber;

begin
A := TComplexNumber.Create(2, 5);
B := TComplexNumber.Create(4, -3);
WriteLn(Format('A = (%s), B = (%s)', [A, B]));
WriteLn(Format('A + B = (%s)', [A + B]));
WriteLn(Format('A - B = (%s)', [A - B]));
end.

This should produce the following output:

[d:\source\delphi.net\code]Project1.exe
A = (2 + 5i), B = (4 - 3i)
A + B = (6 + 2i)
A - B = (-2 + 8i)

[d:\source\delphi.net\code]

C++:

     
test1.h:
#ifndef Test1_H
#define Test1_H

#include

using namespace std;

class ComplexNumber {
public:
int real;
int imaginary;
ComplexNumber operator+(const ComplexNumber&) const;
ComplexNumber operator-(const ComplexNumber&) const;
virtual string toString(void) const;
ComplexNumber(int, int);
};
#endif // Test1_H


test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
#include
#include
#include

using namespace std;

string itos(int i)
{
stringstream s;
s << i;
return s.str();
}

ComplexNumber::ComplexNumber(int real = 0, int imaginary = 0)
{
this->real = real;
this->imaginary = imaginary;
}

ComplexNumber ComplexNumber::operator+(const ComplexNumber& param) const
{
ComplexNumber temp;
temp.real = this->real + param.real;
temp.imaginary = this->imaginary + param.imaginary;
return temp;
}

ComplexNumber ComplexNumber::operator-(const ComplexNumber& param) const
{
ComplexNumber temp;
temp.real = this->real - param.real;
temp.imaginary = this->imaginary - param.imaginary;
return temp;
}

string ComplexNumber::toString() const
{
if (this->imaginary > 0)
return itos(real) + " + " + itos(imaginary) + "i";
else
return itos(real) + " - " + itos(abs(imaginary)) + "i";
}


main.cpp:
#ifndef Test1_H
#include "test1.h"
#endif
#include

using namespace std;

int main()
{
ComplexNumber a (2, 5);
ComplexNumber b (4, -3);
cout << "a = (" << class="string">"), b = (" << class="string">")" << endl;
cout << "a + b = (" << (a + b).toString() << ")" << endl;
cout << "a - b = (" << (a - b).toString() << ")" << endl;
}


makefile:
# Macros

TOOLSROOT=C:\Borland\BCC55
INCLUDEDIR=$(TOOLSROOT)\Include
LIBDIR=$(TOOLSROOT)\Lib;$(TOOLSROOT)\Lib\PSDK
COMPILER=$(TOOLSROOT)\bin\bcc32.exe
COMPILERSWTS=-tWC -c -I$(INCLUDEDIR)
LINKER=$(TOOLSROOT)\bin\ilink32.exe
LINKERSWTS=-ap -Tpe -x -Gn -L$(LIBDIR)
OBJFILES=test1.obj main.obj
LIBFILES=cw32.lib import32.lib
BASEOUTPUTFILE=main
EXEFILE=$(BASEOUTPUTFILE).exe

#implicit rules
.cpp.obj:
$(COMPILER) $(COMPILERSWTS) $<

# Explicit rules

default: $(EXEFILE)

$(EXEFILE): $(OBJFILES)
$(LINKER) $(LINKERSWTS) c0x32.obj $(OBJFILES),$(EXEFILE),,$(LIBFILES),,

clean:
del $(OBJFILES)
del $(EXEFILE)
del $(BASEOUTPUTFILE).tds

This should produce the following output:

[c:\borland\bcc55\projects]main.exe
a = (2 + 5i), b = (4 - 3i)
a + b = (6 + 2i)
a - b = (-2 + 8i)

[c:\borland\bcc55\projects]

C#:

    
test1.cs:
using System;

class ComplexNumber
{
public int Real;
public int Imaginary;

public ComplexNumber(int real, int imaginary) {
this.Real = real;
this.Imaginary = imaginary;
}

public static ComplexNumber operator +(ComplexNumber a, ComplexNumber b) {
return new ComplexNumber(a.Real + b.Real, a.Imaginary + b.Imaginary);
}

public static ComplexNumber operator -(ComplexNumber a, ComplexNumber b) {
return new ComplexNumber(a.Real - b.Real, a.Imaginary - b.Imaginary);
}

public override string ToString() {
if (this.Imaginary > 0)
return (string.Format("{0} + {1}i", this.Real, this.Imaginary));
else
return (string.Format("{0} - {1}i", this.Real, Math.Abs(this.Imaginary)));
}
}


public class MainClass
{
public static void Main() {
ComplexNumber a = new ComplexNumber(2, 5);
ComplexNumber b = new ComplexNumber(4, -3);
Console.WriteLine(string.Format("a = ({0}), b = ({1})", a, b));
Console.WriteLine(string.Format("a + b = ({0})", a + b));
Console.WriteLine(string.Format("a - b = ({0})", a - b));
}
}

This should produce the following output:

[d:\source\csharp\code]test1.exe
a = (2 + 5i), b = (4 - 3i)
a + b = (6 + 2i)
a - b = (-2 + 8i)

[d:\source\csharp\code]

Properties

Properties can be described as object-oriented data members. External to the class, properties look just like data members. Internally, however, properties can be implemented as methods. Properties promote encapsulation by allowing the class to hide the internal representation of its data. Also, by implementing only a getter (also known as an accessor) method or a setter (also known as a mutator) method, a property can be made read-only or write-only respectively, thus allowing the class to provide access control.

Delphi and C# have support for properties built into the language. C++ and Java do not have support for properties built in but can loosely implement this behavior using a get/set convention.

Delphi:

     
Unit1.pas:
unit Unit1;

interface

type
TRectangle = class
private
FHeight: Cardinal;
FWidth: Cardinal;
protected
function GetArea: Cardinal;
public
function ToString: string; virtual;
constructor Create(const Width, Height: Cardinal);
property Area: Cardinal read GetArea;
property Height: Cardinal read FHeight write FHeight;
property Width: Cardinal read FWidth write FWidth;
end;

implementation

uses
SysUtils;

{ TRectangle }

constructor TRectangle.Create(const Width, Height: Cardinal);
begin
inherited Create;
FHeight := Height;
FWidth := Width;
end;

function TRectangle.GetArea: Cardinal;
begin
Result := FWidth * FHeight;
end;

function TRectangle.ToString: string;
begin
Result := Format('Height: %d, Width: %d, Area: %d', [Height, Width, Area]);
end;

end.


Project1.dpr:
program Project1;

{$APPTYPE CONSOLE}

uses
SysUtils,
Unit1 in 'Unit1.pas';

begin
with TRectangle.Create(2, 3) do
try
WriteLn(ToString);
Height := 4;
Width := 3;
WriteLn(ToString);
finally
Free;
end;
end.

This should produce the following output:

[c:\borland\delphi7\projects]Project1.exe
Height: 3, Width: 2, Area: 6
Height: 4, Width: 3, Area: 12

[c:\borland\delphi7\projects]

C#:

    
test1.cs:
using System;

class Rectangle
{
private uint _height;
private uint _width;

public Rectangle(uint width, uint height) {
_width = width;
_height = height;
}

public uint Area {
get { return _width * _height; }
}

public uint Height {
get { return _height; }
set { _height = value; }
}

public uint Width {
get { return _width; }
set { _width = value; }
}

public override string ToString() {
return string.Format("Height: {0}, Width: {1}, Area: {2}", Height, Width, Area);
}
}


public class MainClass
{
public static void Main() {
Rectangle MyRectangle = new Rectangle(2, 3);
Console.WriteLine(MyRectangle);
MyRectangle.Height = 4;
MyRectangle.Width = 3;
Console.WriteLine(MyRectangle);
}
}

This should produce the following output:

[d:\source\csharp\code]test1.exe
Height: 3, Width: 2, Area: 6
Height: 4, Width: 3, Area: 12

[d:\source\csharp\code]

Exceptions

Exceptions provide a uniform mechanism for handling errors. Exceptions are created and raised or thrown when the code enters a condition that it cannot or should not handle by itself. Once the exception is raised or thrown, the exception handling mechanism breaks out of the normal flow of code execution and searches for an appropriate exception handler. If an appropriate exception handler is found, then the error condition is handled in an appropriate manner and normal code execution resumes. Otherwise, the application is assumed to be in an unstable / unpredictable state and exits with an error.

Delphi provides the raise keyword to create an exception and a try...except and try...finally construct (but no unified try...except...finally construct though a try...finally block can be nested within a try...except block to emulate this behavior) for exception handling. Code written in a try block is executed and if an exception occurs, the exception handling mechanism searches for an appropriate except block to handle the exception. The on keyword is used to specify which exceptions an except block can handle. A single except block can contain multiple on keywords. If the on keyword is not used, then the except block will handle all exceptions. Code written in a finally block is guaranteed to execute regardless of whether or not an exception is raised.

    
Unit1.pas:
unit Unit1;

interface

uses
SysUtils;

type
EMyException = class(Exception);

TMyClass = class
public
procedure MyMethod;
constructor Create;
destructor Destroy; override;
end;

implementation

{ TMyClass }

procedure TMyClass.MyMethod;
begin
WriteLn('Entering TMyClass.MyMethod');
raise EMyException.Create('Exception raised in TMyClass.MyMethod!');
WriteLn('Leaving TMyClass.MyMethod'); // this should never get executed
end;

constructor TMyClass.Create;
begin
inherited Create;
WriteLn('In TMyClass.Create');
end;

destructor TMyClass.Destroy;
begin
WriteLn('In TMyClass.Destroy');
inherited Destroy;
end;

end.


Project1.dpr:
program Project1;

{$APPTYPE CONSOLE}

uses
SysUtils,
Unit1 in 'Unit1.pas';

var
MyClass: TMyClass;

begin
MyClass := TMyClass.Create;
try
try
MyClass.MyMethod;
except
on EMyException do
WriteLn(ErrOutput, 'Caught an EMyException!');
end;
finally
MyClass.Free;
end;
end.

This should produce the following output:

[c:\borland\delphi7\projects]Project1.exe
In TMyClass.Create
Entering TMyClass.MyMethod
Caught an EMyException!
In TMyClass.Destroy

[c:\borland\delphi7\projects]

C++ provides the throw keyword for creating an exception and provides a try...catch construct for exception handling. Code written in the try block is executed and if an exception is thrown, the exception handling mechanism looks for an appropriate catch block to deal with the exception. A single try block can have multiple catch blocks associated with it. Catch blocks have a required exception declaration to specify what type of exception it can handle. An exception declaration of (...) means that the catch block will catch all exceptions.

A function can optionally specify what exceptions it can throw via an exception specification in the function declaration. The throw keyword is also used for this purpose. If no exception specification is present then the function can throw any exception.

        
test1.h:
#ifndef Test1_H
#define Test1_H

#include

using std::string;

class MyException {
public:
MyException(string);
string getMessage() const;
private:
string message;
};

class MyClass {
public:
MyClass();
~MyClass();
void myMethod();
};
#endif // Test1_H


test1.cpp:
#ifndef Test1_H
#include "test1.h"
#endif

#include

using std::cout;
using std::endl;

MyException::MyException(string errorMessage)
{
message = errorMessage;
}

string MyException::getMessage() const
{
return message;
}


MyClass::MyClass()
{
cout << "In MyClass::MyClass()" << endl;
}

MyClass::~MyClass()
{
cout << "In MyClass::~MyClass()" << endl;
}

void MyClass::myMethod()
{
cout << "Entering MyClass::myMethod()" << endl;
throw MyException("Exception thrown in MyClass::myMethod()!");
cout << "Leaving MyClass::myMethod()" << class="comment">// this should never get executed
}


main.cpp:
#ifndef Test1_H
#include "test1.h"
#endif

#include

using std::cerr;
using std::endl;

int main()
{
MyClass myClass;
try {
myClass.myMethod();
}
catch(MyException) {
cerr << "Caught a MyException!" << endl;
}
}


makefile:
# Macros

TOOLSROOT=C:\Borland\BCC55
INCLUDEDIR=$(TOOLSROOT)\Include
LIBDIR=$(TOOLSROOT)\Lib;$(TOOLSROOT)\Lib\PSDK
COMPILER=$(TOOLSROOT)\bin\bcc32.exe
COMPILERSWTS=-tWC -c -I$(INCLUDEDIR)
LINKER=$(TOOLSROOT)\bin\ilink32.exe
LINKERSWTS=-ap -Tpe -x -Gn -L$(LIBDIR)
OBJFILES=test1.obj main.obj
LIBFILES=cw32.lib import32.lib
BASEOUTPUTFILE=main
EXEFILE=$(BASEOUTPUTFILE).exe

#implicit rules
.cpp.obj:
$(COMPILER) $(COMPILERSWTS) $<

# Explicit rules

default: $(EXEFILE)

$(EXEFILE): $(OBJFILES)
$(LINKER) $(LINKERSWTS) c0x32.obj $(OBJFILES),$(EXEFILE),,$(LIBFILES),,

clean:
del $(OBJFILES)
del $(EXEFILE)
del $(BASEOUTPUTFILE).tds

This should produce the following output:

[c:\borland\bcc55\projects]main.exe
In MyClass::MyClass()
Entering MyClass::myMethod()
Caught a MyException!
In MyClass::~MyClass()

[c:\borland\bcc55\projects]

C# uses the throw keyword to throw an exception and provides try...catch, try...finally and try...catch...finally constructs for exception handling. Code in the try block is executed and if an exception is thrown, the exception handling mechanism searches for an appropriate catch block. A single try block can have multiple catch blocks associated with it. A catch block can declare the exception type that it handles. If a catch clause does not specify the exception type it will catch all exceptions thrown. Code in a finally block will be executed regardless of whether or not an exception is thrown.

    
using System;

class MyException: Exception { };

class MyClass
{
public MyClass() {
Console.WriteLine("In MyClass.MyClass()");
}

~MyClass() {
Console.WriteLine("In MyClass.~MyClass()");
}

public void MyMethod() {
Console.WriteLine("Entering MyClass.MyMethod()");
throw new MyException();
Console.WriteLine("Leaving MyClass.MyMethod()"); // this should never get executed
}
}

public class MainClass
{
public static void Main() {
MyClass myClass = new MyClass();
try {
myClass.MyMethod();
}
catch (MyException) {
Console.Error.WriteLine("Caught a MyException!");
}
finally {
Console.WriteLine("In finally block"); // will always get executed
}
}
}

This should produce similar output:

[d:\source\csharp\code]test1.exe
In MyClass.MyClass()
Entering MyClass.MyMethod()
Caught a MyException!
In finally block
In MyClass.~MyClass()

[d:\source\csharp\code]

Java uses the throw keyword to throw an exception and provides try...catch, try...finally and try...catch...finally constructs for exception handling. Code inside a try block is executed and if an exception is thrown, the exception handling mechanism searches for an appropriate catch block. A single try block can have multiple catch blocks associated with it. Catch blocks are required to specify the exception type that they handle. Code in a finally block will be executed regardless of whether or not an exception is thrown.

Java also requires that methods either catch or specify all checked exceptions that can be thrown within the scope of the method. This is done using the throws clause of the method declaration.

    
Test1.java:
class MyException extends Exception { };

class MyClass {
public MyClass() {
System.out.println("In MyClass.MyClass()");
}
protected void finalize() throws Throwable {
System.out.println("In MyClass.finalize()");
}
public void myMethod() throws MyException {
System.out.println("Entering myClass.myMethod()");
throw new MyException();
}
}

public class Test1 {
public static void main(String args[]) {
MyClass myClass = new MyClass();
try {
myClass.myMethod();
}
catch(MyException e) {
System.err.println("Caught a MyException!");
}
finally {
System.out.println("In finally block"); // will always get executed
}
}
}

This should produce similar output:

[d:\source\java\code]java.exe Test1
In MyClass.MyClass()
Entering myClass.myMethod()
Caught a MyException!
In finally block

[d:\source\java\code]

Generics and Templates

Templates and Generics are constructs for parametric polymorphism. That is, they make creating parameterized types for creating type safe collections. However, they do not refer to the same thing i.e. templates are not generics and generics are not templates. For more information on what the differences are, refer to the following external links:

Currently, only the C++ language supports templates. Generic support is planned for version 2.0 of the C# language and for version 1.5 of the Java language.

C++:

     
stacktemplate.h:
#ifndef StackTemplate_H
#define StackTemplate_H

#define EMPTYSTACK -1

template<class T>
class Stack {
public:
Stack(int);
~Stack();
bool push(const T&);
bool pop(T&);
private:
bool isEmpty() const;
bool isFull() const;
int stackSize;
int stackTop;
T* stackPtr;
};

template<class T>
Stack::Stack(int maxStackSize)
{
stackSize = maxStackSize;
stackTop = EMPTYSTACK;
stackPtr = new T[stackSize];
}

template<class T>
Stack::~Stack()
{
delete [] stackPtr;
}

template<class T>
bool Stack::push(const T& param)
{
if (!isFull()) {
stackPtr[++stackTop] = param;
return true;
}
return false;
}

template<class T>
bool Stack::pop(T& param)
{
if (!isEmpty()) {
param = stackPtr[stackTop--];
return true;
}
return false;
}

template<class T>
bool Stack::isEmpty() const
{
return stackTop == EMPTYSTACK;
}

template<class T>
bool Stack::isFull() const
{
return stackTop == stackSize - 1;
}

#endif


main.cpp:
#ifndef StackTemplate_H
#include "stacktemplate.h"
#endif
#include

using std::cout;
using std::endl;

int main()
{
Stack<int> intStack (5);
int i = 2;
while(intStack.push(i)) {
cout << "pushing " << class="string">" onto intStack" << endl;
i += 2;
}
while(intStack.pop(i)) {
cout << "popped " << class="string">" from intStack" << endl;
}
cout << endl;

Stack<float> floatStack (5);
float f = 2.1;
while(floatStack.push(f)) {
cout << "pushing " << class="string">" onto floatStack" << endl;
f += 2.1;
}
while(floatStack.pop(f)) {
cout << "popping " << class="string">" from floatStack" << endl;
}
}

This should produce the following output:

[c:\borland\bcc55\projects]main.exe
pushing 2 onto intStack
pushing 4 onto intStack
pushing 6 onto intStack
pushing 8 onto intStack
pushing 10 onto intStack
popped 10 from intStack
popped 8 from intStack
popped 6 from intStack
popped 4 from intStack
popped 2 from intStack

pushing 2.1 onto floatStack
pushing 4.2 onto floatStack
pushing 6.3 onto floatStack
pushing 8.4 onto floatStack
pushing 10.5 onto floatStack
popping 10.5 from floatStack
popping 8.4 from floatStack
popping 6.3 from floatStack
popping 4.2 from floatStack
popping 2.1 from floatStack

[c:\borland\bcc55\projects]

Further Reading

For more information, refer to the following links:

0 Comments: