Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Visual C++ 2005 (2006) [eng]-1

.pdf
Скачиваний:
108
Добавлен:
16.08.2013
Размер:
18.66 Mб
Скачать

Class Inheritance and Virtual Functions

(HandlerClass::Fun1) from the list for handler and create a new object pointing to the functions that remain.

Note that the invocation list for a delegate must contain at least one function pointer. If you remove all the function pointers using the subtraction operator then the result will be nullptr.

When you use the delegate constructor that has two parameters the first argument is a reference to an object on the CLR heap and the second object is the address of an instance function for that object’s type. Thus this constructor creates a delegate that contains a pointer to the instance function specified by the second argument for the object specified by the first argument. Here’s how you can create such a delegate:

HandlerClass^ obj = gcnew HandlerClass;

Handler^ handler2 = gcnew Handler (obj, &HandlerClass::Fun3);

The first statement creates an object and the second statement creates a delegate pointing to the Fun3() function for the HandlerClass object obj. The delegate expects an argument of type int so you can invoke it with the statement:

handler2(70);

This results in Fun3() for obj being called with an argument value of 70, so the output is:

Function3 called with value 71

The value stored in the value field for obj is 1 because you create the object using the default constructor. The statement in the body of Fun3() adds the value field to the function argument — hence the 71 in the output.

Because they are both of the same type, you could combine the invocation list for handler with the list for the handler2 delegate:

Handler^ handler

= gcnew Handler(HandlerClass::Fun1);

// Delegate object

handler += gcnew

Handler(HandlerClass::Fun2);

 

HandlerClass^ obj = gcnew HandlerClass;

Handler^ handler2 = gcnew Handler (obj, &HandlerClass::Fun3); handler += handler2;

Here you recreate handler to reference a delegate that contains pointers to the static Fun1() and Fun2() functions. You then create a new delegate referenced by handler that contains the static functions plus the Fun3() instance function for obj. You can now invoke the delegate with the statement:

handler(50);

This results in the following output:

Function1 called with value 50

Function2 called with value 50

Function3 called with value 51

Press any key to continue . . .

539

Chapter 9

As you see, invoking the delegate calls the two static functions plus the Fun3() member of obj, so you can combine static and non-static functions with a single invocation list for a delegate.

Let’s put some of the fragments together in an example to make sure it does really work.

Try It Out

Creating and Calling Delegates

Here’s a potpourri of what you have seen so far about delegates:

//Ex9_17.cpp : main project file.

//Creating and calling delegates

#include “stdafx.h”

using namespace System;

public ref class HandlerClass

{

public:

static void Fun1(int m)

{ Console::WriteLine(L”Function1 called with value {0}”, m); }

static void Fun2(int m)

{ Console::WriteLine(L”Function2 called with value {0}”, m); }

void Fun3(int m)

{ Console::WriteLine(L”Function3 called with value {0}”, m+value); }

void Fun4(int m)

{ Console::WriteLine(L”Function3 called with value {0}”, m+value); }

HandlerClass():value(1){}

HandlerClass(int m):value(m){} protected:

int value;

};

public delegate void Handler(int value);

// Delegate declaration

int main(array<System::String ^> ^args)

{

Handler^ handler = gcnew Handler(HandlerClass::Fun1); // Delegate object Console::WriteLine(L”Delegate with one pointer to a static function:”); handler->Invoke(90);

handler += gcnew Handler(HandlerClass::Fun2); Console::WriteLine(L”\nDelegate with two pointers to static functions:”); handler->Invoke(80);

HandlerClass^ obj = gcnew HandlerClass;

Handler^ handler2 = gcnew Handler (obj, &HandlerClass::Fun3); handler += handler2;

540

Class Inheritance and Virtual Functions

Console::WriteLine(L”\nDelegate with three pointers to functions:”); handler(70);

Console::WriteLine(L”\nShortening the invocation list...”);

handler -= gcnew Handler(HandlerClass::Fun1); Console::WriteLine

(L”\nDelegate with pointers to one static and one instance function:”); handler(60);

}

This example produces the following output:

Delegate with one pointer to a static function:

Function1 called with value 90

Delegate with two pointers to static functions:

Function1 called with value 80

Function2 called with value 80

Delegate with three pointers to functions:

Function1 called with value 70

Function2 called with value 70

Function3 called with value 71

Shortening the invocation list...

Delegate with pointers to one static and one instance function:

Function2 called with value 60

Function3 called with value 61

Press any key to continue . . .

How It Works

You saw all the operations that appear in main() in the previous section. You invoke a delegate using the Invoke() function explicitly and by just using the delegate handle followed by its argument list. You can see from the output that everything works as it should.

Although the example shows a delegate that can contain pointers to functions with a single argument, a delegate can point to functions with as many arguments as you want. For example, you could declare a delegate type like this:

delegate void MyHandler(double x, String^ description);

This statement declares the MyHandler delegate type that can only point to functions with a void return type and two parameters, the first of type double and the second of type String^.

Unbound Delegates

The delegates you have seen up to now have been examples of bound delegates. They are called bound delegates because they each have a fixed set of functions in their invocation list. You can also create unbound delegates; an unbound delegate points to an instance function with a given parameter list and return type for a given type of object. Thus the same delegate can invoke the instance function for any object of the specified type. Here’s an example of declaring an unbound delegate:

541

Chapter 9

public delegate void UBHandler(ThisClass^, int value);

The first argument specifies the type of the this pointer for which a delegate of type UBHandler can call an instance function; the function must have a single parameter of type int and a return type of void. Thus a delegate of type UBHandler can only call a function for an object of type ThisClass but for any object of that type. This may sound a bit restrictive but turns out to be quite useful; you could use the delegate to call a function for each element of type ThisClass^ in an array for example.

You can create a delegate of type UBHandler like this:

UBHandler^ ubh = gcnew UBHandler(&ThisClass::Sum);

The argument to the constructor is the address of a function in the ThisClass class that has the required parameter list and return type.

Here’s a definition for ThisClass:

public ref class ThisClass

{

public:

void Sum(int n, String^ str)

{ Console::WriteLine(L”Sum result = {0}”, value + n); }

void Product(int n, String^ str)

{ Console::WriteLine(L”Product result = {0}”, value*n); }

ThisClass(double v) : value(v){}

private: double value;

};

The Sum() function is a public instance member of the ThisClass class so invoking the ubh delegate will call the Sum() function for any object of this class type.

When you call an unbound delegate, the first argument is the object for which the functions in the invocation list are to be called, and the subsequent arguments are the arguments to those functions. Here’s how you might call the ubh delegate:

ThisClass^ obj = gcnew ThisClass(99.0);

ubh(obj, 5);

The first argument is a handle to a ThisClass object that you created on the CLR heap by passing the value 99.0 to the class constructor. The second argument to the ubh call is 5, so it results in the Sum() function being called with an argument of 5 for the object referenced by obj.

You can combine unbound delegates using the + operator to create a delegate that calls multiple functions. Of course, all the functions must be compatible with the delegate, so for ubh they must be instance functions in the ThisClass class that have one parameter of type int and a void return type. Here’s an example:

ubh += gcnew UBHandler(&ThisClass::Product);

542

Class Inheritance and Virtual Functions

Invoking the new delegate referenced by ubh calls both the Sum() and Product() functions for an object of type ThisClass. Let’s see it in action.

Try It Out

Using an Unbound Delegate

This example uses the code fragments from the previous section to demonstrate the operation of an unbound delegate:

//Ex9_18.cpp : main project file.

//Using an unbound delegate

#include “stdafx.h”

using namespace System;

public ref class ThisClass

{

public:

void Sum(int n)

{ Console::WriteLine(L”Sum result = {0} “, value+n); }

void Product(int n)

{ Console::WriteLine(L”product result = {0} “, value*n); }

ThisClass(double v) : value(v){}

private: double value;

};

public delegate void UBHandler(ThisClass^, int value);

int main(array<System::String ^> ^args)

{

array<ThisClass^>^ things = { gcnew ThisClass(5.0),gcnew ThisClass(10.0), gcnew ThisClass(15.0),gcnew ThisClass(20.0), gcnew ThisClass(25.0)

};

UBHandler^ ubh = gcnew UBHandler(&ThisClass::Sum); // Create a delegate object

// Call the delegate for each things array element for each(ThisClass^ thing in things)

ubh(thing, 3);

ubh += gcnew UBHandler(&ThisClass::Product);

// Add a function to the delegate

// Call the new delegate for each things array element for each(ThisClass^ thing in things)

ubh(thing, 2);

return 0;

}

543

Chapter 9

This example produces the following output:

Sum result = 8 Sum result = 13 Sum result = 18 Sum result = 23 Sum result = 28 Sum result = 7

product result = 10 Sum result = 12 product result = 20 Sum result = 17 product result = 30 Sum result = 22 product result = 40 Sum result = 27 product result = 50

Press any key to continue . . .

How It Works

The UBHandler delegate type is declared by the following statement.

public delegate void UBHandler(ThisClass^, int value);

UBHandler delegate object is unbound delegates that can call instance functions for objects of type ThisClass as long as they have a single parameter of type int and a return type of void.

The ThisClass class definition in the example is the same as you saw in the previous section. It has two instance functions — Sum() and Product() — that have a parameter type of int and a return type of void so either or both may be called by a delegate of type UBHandler.

You create an array of handles to ThisClass objects in main() with the statement:

array<ThisClass^>^ things = { gcnew ThisClass(5.0),gcnew ThisClass(10.0), gcnew ThisClass(15.0),gcnew ThisClass(20.0), gcnew ThisClass(25.0)

};

The five objects in the initialization list each encapsulate a different value of type double so for Sum() and Product() function calls the object involved will be easy to identify in the output.

You create a delegate object with the statement:

UBHandler^ ubh = gcnew UBHandler(&ThisClass::Sum); // Create a delegate object

Invoking the delegate object referenced by the handle ubh calls the Sum() function for any object of type ThisClass, and you do this for each object in the things array:

for each(ThisClass^ thing in things) ubh(thing, 3);

544

Class Inheritance and Virtual Functions

The for each loop iterates over each element in the things array so in the loop body you call the delegate with an element from the array as the first argument. This causes the Sum() function to be called for the thing object with an argument of 3. Thus this loop produces the first five output lines.

Next you create a new delegate:

ubh += gcnew UBHandler(&ThisClass::Product);

// Add a function to the delegate

This statement creates a new UBHandler delegate that points to the Product() function and combines this with the existing delegate referenced by ubh. The result is another delegate that has pointers to both the Sum() and Product() functions in its invocation list.

The last loop calls the ubh delegate for each element in the things array with the argument value 2. The result will be that both Sum() and Product() will be called for each ThisClass object with the argument value 2, so the loop produces the next ten lines of output.

Although you have used unbound delegates in a very simple way, here they provide immense flexibility in your programs. You could pass an unbound delegate as an argument to a function, for example, to enable the same function to call different combinations of instance functions at different times so the delegate becomes a kind of function selector. The sequence in which functions are called by a delegate is the sequence that they appear in the invocation list so a delegate provides you with the means of controlling the sequence in which functions are called.

Creating Events

As I said earlier, the signaling of an event involves a delegate and the delegate contains pointers to the functions that are to be called when the event occurs. Most of the events you work with in your programs are events associated with controls such as buttons or menu items and these events arise from user interactions with your program, but you can also define and trigger events in your own program code.

An event is a member of a reference class that you define using the event keyword and a delegate class name:

public delegate void DoorHandler(String^ str);

// Class with an event member public ref class Door

{

public:

//An event that will call functions associated

//with an DoorHandler delegate object

event DoorHandler^ Knock;

// Function to trigger events void TriggerEvents()

{

Knock(“Fred”);

Knock(“Jane”);

}

};

545

Chapter 9

The Door class has an event member with the name Knock that corresponds to a delegate of type DoorHandler. Knock is an instance member of the class, but you can specify an event as a static class member using the static keyword. You can also declare an event to be virtual. When a Knock event is triggered, it can call functions with the parameter list and return type that are specified by the DoorHandler delegate.

The Door class also has a public function, TriggerEvent() that triggers two Knock events, each with different arguments. The arguments are passed to the functions that have been registered to receive notification of the Knock event. As you see, triggering an event is essentially the same as calling a delegate.

You could define a class that might handle Knock events like this:

public ref class AnswerDoor

{

public:

void ImIn(String^ name)

{

Console::WriteLine(L”Come in {0}, it’s open.”,name);

}

void ImOut(String^ name)

{

Console::WriteLine(L”Go away {0}, I’m out.”,name);

}

};

The AnswerDoor class has two public function members that potentially could handle a Knock event because they both have the parameter list and return type identified in the declaration of the

DoorHandler delegate.

Before you can register functions that are to receive notifications of Knock events, you need to create a Door object. You can create a Door object like this:

Door^ door = gcnew Door;

Now you can register a function to receive notification of the Knock event in the door object like this:

AnswerDoor^ answer = gcnew AnswerDoor;

door->Knock += gcnew DoorHandler(answer, &AnswerDoor::ImIn);

The first statement creates an object of type AnswerDoor — you need this because the ImIn() and ImOut() functions are not static class members. You then add an instance of the DoorHandler delegate type to the Knock member of door. This exactly parallels the process of adding function pointers to a delegate and you could add further handler functions to be called when a Knock event is triggered in the same way. We can see it operating in an example.

546

Class Inheritance and Virtual Functions

Try It Out

Handling Events

This example uses the classes from the preceding section to define, trigger, and handle events:

//Ex9_19.cpp : main project file.

//Defining, triggering and handling events. #include “stdafx.h”

using namespace System;

public delegate void DoorHandler(String^ str);

// Class with an event member public ref class Door

{

public:

//An event that will call functions associated

//with an DoorHandler delegate object

event DoorHandler^ Knock;

// Function to trigger events void TriggerEvents()

{

Knock(L”Fred”);

Knock(L”Jane”);

}

};

// Class defining handler functions for Knock events public ref class AnswerDoor

{

public:

void ImIn(String^ name)

{

Console::WriteLine(L”Come in {0}, it’s open.”,name);

}

void ImOut(String^ name)

{

Console::WriteLine(L”Go away {0}, I’m out.”,name);

}

};

int main(array<System::String ^> ^args)

{

Door^ door = gcnew Door;

AnswerDoor^ answer = gcnew AnswerDoor;

// Add handler for Knock event member of door

door->Knock += gcnew DoorHandler(answer, &AnswerDoor::ImIn);

door->TriggerEvents(); // Trigger Knock events

// Change the way a knock is dealt with

547

Chapter 9

door->Knock -= gcnew DoorHandler(answer, &AnswerDoor::ImIn); door->Knock += gcnew DoorHandler(answer, &AnswerDoor::ImOut); door->TriggerEvents(); // Trigger Knock events return 0;

}

Executing this example results in the following output:

Come in Fred, it’s open.

Come in Jane, it’s open.

Go away Fred, I’m out.

Go away Jane, I’m out.

Press any key to continue . . .

How It Works

You first create two objects in main():

Door^ door = gcnew Door;

AnswerDoor^ answer = gcnew AnswerDoor;

The door object has an event member, Knock, and the answer object has member functions that can be registered to be called for Knock events.

The next statement registers the ImIn() member of the answer object to receive notification of Knock events for the door object:

door->Knock += gcnew DoorHandler(answer, &AnswerDoor::ImIn);

If it made sense to do so, you could register other functions to be called when a Knock event is triggered.

The next statement calls the TriggerEvents() member of the door object:

door->TriggerEvents();

// Trigger Knock events

This results in two Knock events, one with the argument “Fred” and the other with the argument “Jane”. The result is that the ImIn() function is called once for each event, which produces the first two lines of output.

Of course, you might want to respond differently to an event at different times, depending on the circumstances, and this is what the next three statements in main() demonstrate:

door->Knock -= gcnew DoorHandler(answer, &AnswerDoor::ImIn); door->Knock += gcnew DoorHandler(answer, &AnswerDoor::ImOut); door->TriggerEvents(); // Trigger Knock events

The first statement removes the pointer to the ImIn() function from the event and the second statement registers the ImOut() function for the answer object to receive event notifications. When the Knock events are triggered by the third statement, the ImOut() function is called so the results are a little different.

548