Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:
реклама
переводы денег из США в Россию , Новостройки Василеостровский район

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Реализация обработки событий на C++

© Олег Сотников, oleg_s_@rambler.ru

Событием (event) называется исходящий вызов. Этот термин, наверное, хорошо знаком тем, кто работает с такими языками, как Delphi, Visual Basic и т.д. При возникновении события происходит вызов всех его обработчиков. Так как объект-инициатор события может ничего не знать об обработчиках, то событие называют исходящим вызовом. Работа события происходит по принципу "от одного объекта к нескольким". Важно отметить, что событие (event) и сообщение (message) это разные понятия. Сообщением называется прямой вызов, который передаётся от объекта к объекту. То есть у сообщения имеется один обработчик.

События применяются довольно широко. Примером могут служить всевозможные библиотеки, реализующие графический интерфес пользователя. Но события при правильном применении могут оказаться ДЕЙСТВИТЕЛЬНО ПОЛЕЗНОЙ ВЕЩЬЮ К сожалению исторически сложилось так, что в C++ нет событий. Поэтому при необходимости разработчики реализуют их на уровне библиотеки. Здесь вашему вниманию представлена реализация одной такой библиотеки. В ней есть два класса: Delegate и Event.

Класс Delegate (делегат)

Класс делегат расширяет понятие указателя на функцию. Используя этот класс Вы можете передать ссылку на метод объекту-делегату. Далее объект-делегат может быть передан клиентскому коду, который может косвенно вызвать метод. При этом код, который обращается с делегатом не знает что за метод скрывается за ним. Преимущество делегата состоит в том, что Вы можете динамически передавать ему указатели на методы и тем самым изменять поведение программы без перекомпиляции клиентского кода. Программа, использующая делегат будет оставаться в счастливом неведении о ваших манипуляциях с методами и их обладателями.

В архиве, который прилагается к статье имеется заголовочный файл Delegate.h, в котором находится объявление и реализация класса Delegate. Для того, чтобы понять как используется этот класс рассмотрим пару примеров.

Примеры использования класса Delegate

Пример №1

#include &ltiostream&gt
using namespace std;

#include "Delegate.h"

class Test
{
public:
    Test()
    {
        //Передаем делегату указатель на функцию
        _delegate = Delegate(this, &ampTest::SayHello);
    }

    void SayHello()
    {
        cout 


Результаты работы этой программы должны быть следующими:

Разберём программу подробнее. Вначале мы подключаем заголовочный файл класса Delegate:

    #include "Delegate.h"

Затем делаем объявление простого класса Test. Этот класс содержит в сбе частную переменную делегат. В неё мы будем подставлять функции нашего класса. Первым делом в конструкторе Test передаём делегату указатель на функцию Test::SayHello(). Как видно из реализации функции main, сразу после конструирования экземпляра Test происходит вызов функции Test::RunTest(). В ней то и заключена основная суть программы. После начала выполнения функции RunTest() делается вызов делегата как обычной функции C++

    void RunTest()
    {
        //Делаем вызов делегата первый раз
        _delegate();
        ...

Делегат передаёт этот вызов той функции, на которую он указывает. Если же делегат содержит NULL, то вызов игноррируется. Далее мы меняем функцию, на которую указывает делегат:

        ...
        _delegate = Delegate(this, &ampTest::SayGoodBye);
        //Второй раз вызываем делегат
        _delegate();
    }

Для демонстрации полученного эффекта повторно делаем вызов. При этом происходит вызов функции Test::SayGoodBye().

Данный пример довольно прост, но всё же он демонстрирует возможности, которыми обладают делегаты и их основные преимущества.

Пример №2: Небольшая база данных

Теперь после первого знакомства с делегатами пришло время рассмотреть более сложный приём и понять, как можно применить делегаты в своих проектах. Предположим, что Вы проектируете небольшую базу данных для какого-нибудь предприятия. Эта база данных должна содержать в себе сведения о работниках предприятия. Эти сведения включают в себя:

  • Имя работника
  • Его оклад
  • Размер начисляемой премии (в %)
  • Категорию

Размер заработной платы определяется с учётом премии. Добавим в программу класс служащего (Employe)

  • Объявление класса Employe находится в файле Employe.h
  • Реализация класса Employe находится в файле Employe.cpp

Как видно из объявления класса он содержит в себе четыре частные переменные, которые содержат имя работника, его оклад, размер премии и категорию. Так как эти переменные частные, то доступ к ним осуществляется при помощи специальных функций. Также предоставляется специальный метод CalculateWage(), который возвращает зарплату работника в зависимости от премии

Следующий класс, который мы добавим в наш проект будет представлять собой базу данных предприятия по рабочим. Назовём этот класс EnterpriseDB. Этот класс будет обладать минимальной функциональностью, но всё же он будет являться центральной частью нашего проекта.

  • Объявление класса EnterpriseDB находится в файле EnterpriseDB.h
  • Реализация класса EnterpriseDB находится в файле EnterpriseDB.cpp

Класс EnterpriseDB в сущности представляет собой коллекцию указателей на объекты Employe. Имеется один метод для вставки объектов (EnterpriseDB::Add(std::string, int)). Метод принимает в качестве параметров имя работника и его категорию. Затем происходит выделение памяти под объект Employe* и вставка его в конец коллекции. В деструкторе EnterpriseDB уничтожает все указатели Employe*. С помощью метода SetupPayments(Employe *) происходит распределение окладов и премий в зависимости от категории служащего.

Но самой важной функцией в классе, а возможно и во всей программе является функция ProcessStaff(Delegate &). С помощью этого метода можно для каждого из работников, находящихся в коллекции _staff произвести вызов метода, который скрывается за делегатом. Далее в основной функции программы main производятся несколько таких вызовов.

Всякому предприятию жизненно необходим свой бухгалтер. Наше предприятие не является исключением, поэтому настало время добавить класс, который реализует простую бухгалтерию. Класс будет называться Accountant (от англ. "бухгалтер").

  • Объявление класса Accountant находится в файле Accountant.h
  • Реализация класса Accountant находится в файле Accountant.cpp

Класс бухгалтера предоставляет следующие возможности. Он позволяет вести учёт рабочего с помощью метода AddEmployeToAccount(Employe *). При выполнении этого метода счётчик работников _count увеличивается на 1, а к общей зарплате _totalWage добавляется зарплата данного работника, которая вычисляется с помощью метода Employe::CalculateWage(). Метод GetTotalWage() возвращает количество денег, которые выплачиваются всем рабочим в качестве зарплаты. GetAverageWage() вычисляет среднюю зарплату по предприятию.

Наконец пришло время написать главную функцию программы. Её реализация представлена ниже


#include &ltiostream&gt
using namespace std;

#include "Employe.h"
#include "EnterpriseDB.h"
#include "Accountant.h"
#include "Delegate.h"

class EmployeTester
{
public:

    // Распечатать информацию о работнике
    void PrintInfo(Employe *employe);

    // Нанять несколько работников
    void AddEmployes(EnterpriseDB *db);
};

int main()
{
    EmployeTester *test = new EmployeTester;
    EnterpriseDB *db = new EnterpriseDB;
    Accountant *acc = new Accountant;

    test->AddEmployes(db);

    db->ProcessStaff(Delegate(db, &EnterpriseDB::SetupPayments));
    cout ProcessStaff(Delegate(test, &EmployeTester::PrintInfo));

    db->ProcessStaff(Delegate(acc, &Accountant::AddEmployeToAccount));
    cout GetCount() GetTotalWage() GetAverageWage() GetName() GetCategory() GetSalary() GetBonus() * 100 Add("Gates Bill", 1);
    db->Add("Bush George", 2);
    db->Add("Payne Max", 3);
}

Результат выполнения программы приведён на рисунке ниже

Разберёмся, что же происходит в этой программе. Во-первых, следует отметить новый класс, который выполняет в основном вспомогательные функции. Этот класс называется EmployeTester. Он имеет два метода. Первый из них - PrintInfo(Employe *) распечатывает информацию о сотруднике, который передаётся ему в качестве параметра. Второй метод AddEmployes(EnterpriseDB *) просто заполняет базу данных несколькими сотрудниками.

Ну а сейчас перейдём к рассмотрению главной функции программы. Именно в ней происходят самые главные события, связанные с использованием делегатов. Сначала программа создаёт базу данных, бухгалтера и объект EmployeTester:

    EmployeTester *test = new EmployeTester;
    EnterpriseDB *db = new EnterpriseDB;
    Accountant *acc = new Accountant;

Затем в базу данных вносятся сведения о трёх работниках:

    test->AddEmployes(db);

А теперь начинается самое важное. С помощью метода EnterpriseDB::ProcessStaff происходит назначение всем работникам зарплат и премий. Для этого методу ProcessStaff передаётся в качестве параметра делегат, который указывает на функцию EnterpriseDB::SetupPayments. Соответственно происходит вызов этой функции для каждого из работников в базе данных. Так служащему правильно назначается зарплата и премия в соответствии с его категорией:

    db->ProcessStaff(Delegate(db, &EnterpriseDB::SetupPayments));

После установки начислений служащим в дело вступает объект EmployeTester. Он выводит на экран информацию о каждом из рабочих:

    db->ProcessStaff(Delegate(test, &EmployeTester::PrintInfo));

А сейчас пришло время бухгалтеру подсчитать всех рабочих и определить суммарную зарплату и среднюю зарплату по предприятию. Результаты выводятся на экран:

    db->ProcessStaff(Delegate(acc, &Accountant::AddEmployeToAccount));
    cout GetCount() GetTotalWage() GetAverageWage() 


Подведём итоги. Делегаты удобно применять в следующих случаях:

  • Нужно вызывать только один метод
  • Вызывающему коду не нужно знать об объекте, которому принадлежит метод
  • Класс может иметь несколько реализаций одного метода

Класс Event (событие)

Событие в данном случае - это способ для класса оповестить клиентов (классов, использующих данный класс) о том, что с ним произошло что-то интересное. Наверное самый знакомый способ использования событий - в графическом пользовательском интерфейсе (GUI). Обычно классы, представляющие элементы управления (controls) в графическом интерфейсе имеют события, которые срабатывают, когда пользователь что-либо делает с элементом управления (например, нажимает на кнопку). Но события могут быть использованы не только в графическом интерфейсе. События обеспечиают действительно полезный путь для объектов сигнализировать об изменении своего состояния. Причём это изменение может быть важно для клиентов данного объекта. События - это важные строительные блоки для создания классов, которые могут быть повторно использованы во многих программах. События используются непосредственно вместе с делегатами. Важно помнить, что объекты - делегаты инкапсултруют (скрывают) метод класса и он может быть вызван анонимно. Событие - это способ вызвать методы других объектов, когда событие срабатывает. То есть сторонний объект помещает указатель на свой метод в делегат (как было показано ранее) и передаёт этот делегат событию. При срабатывании события вызывается делегат, а следовательно и тот метод, который был ему передан.

Пример использования класса Event

#include &ltiostream&gt
#include &ltstring&gt
using namespace std;

#include "Event.h"

// Цвет
class Color
{
public:

    Color(unsigned short r, unsigned short g, unsigned short b,
        std::string displ)
        : red(r), green(g), blue(b), display(displ)
    {
    }

    Color(const Color &color)
        : red(color.red), green(color.green), blue(color.blue)
        , display(color.display)
    {
    }

    std::string ToString() const
    {
        return display;
    }

    static const Color Black;
    static const Color White;
    static const Color Red;
    static const Color Green;
    static const Color Blue;

private:
    unsigned short red, green, blue;
    std::string display;
};

const Color Color::Black(0, 0, 0, "Black");
const Color Color::White(255, 255, 255, "White");
const Color Color::Red(255, 0, 0, "Red");
const Color Color::Green(0, 255, 0, "Green");
const Color Color::Blue(0, 0, 255, "Blue");

// Элемент, вид - можно назвать по-разному
class View
{
public:

    // Событие, срабатывающее при смене цвета
    Event ColorChanged;

    View(): _color(Color::White)
    {
    }

    Color GetColor() const
    {
        return _color;
    }

    void SetColor(const Color &color)
    {
        _color = color;
        ColorChanged();
    }

private:
    Color _color;
};

// Производный класс от View
class NewView: public View
{
public:

    NewView()
    {
        // Добавляем функцию реакции на событие
        ColorChanged += Delegate(this, &NewView::OnColorChange);
    }

    void OnColorChange()
    {
        cout ColorChanged += Delegate(this, &EventListener::React);
    }

    void React()
    {
        cout ColorChanged -= Delegate(this, &EventListener::React);
        _pView = NULL;
    }

private:
    View *_pView;
};

int main()
{
    NewView nv;
    EventListener el(&nv);
    cout 

Результат выполнения программы приведён ниже на рисунке.

Обсудим код. В этой программе задействованы три класса. Сначала рассмотрим класс Color, который описывает цвет. В нём есть следующие компоненты:

  • Красная, зелёная и синяя составляющие цвета (RGB)
  • Строка display, предназнвченная для вывода цвета на экран

Для удобства обращения с классом объявлены несколько статических констант - White, Black, Red, Green, Blue. Использовать класс Color можно следующим образом:

    Color c1, c2;
    //Теперь с1 содержит красный цвет, а с2 - чёрный
    c1 = Color::Red;
    c2 = Color::Black;
    //strC1 содержит "Red"
    std::string strC1 = c1.ToString();

Функция ToString() преобразует значение цвета в строку.

Следующий класс называется View. Он содержит в себе одну переменную определяющую цвет. Класс предоставляет методы для получения/установки значения цвета - это соответственно GetColor/SetColor. Также в классе View есть событие ColorChanged , которое срабатывает, когда программа изменяет значение цвета, то есть вызывает метод SetColor.

NewView - является производным классом от View. Его работа заключается в том, чтобы добавить свою функцию в событие ColorChanged, которое принадлежит его предку. Делается это в конструкторе NewView:

    NewView()
    {
        // Добавляем функцию реакции на событие
        ColorChanged += Delegate(this, &NewView::OnColorChange);
    }

Как видно, для того, чтобы передать свою функцию событию нужно сначала создать для неё обёртку ввиде делегата, а затем "прибавить" делегат к событию оператором +=.

"Наблюдатель" за событиями (EventListener) фактически поступает также как и класс NewView. Он получает указатель на View и добавляет к событию ColorChanged свой метод EventListener::React():

    EventListener(View *view)
        : _pView(view)
    {
        // Присоединяем функцию класса EventListener
        _pView->ColorChanged += Delegate(this, &EventListener::React);
    }

    void React()
    {
        cout 

Метод EventListener::Detach() отсоединяет от события свой метод React:

    // Отсоединить функцию
    void Detach()
    {
        _pView->ColorChanged -= Delegate(this, &EventListener::React);
        _pView = NULL;
    }

Отсоединение метода происходит также как и присоединение, только используется оператор -=.

Рассмотрим главную функцию программы. Сначала программа создаёт объекты EventListener и NewView и выводит начальное значение цвета в объекте NewView:

    NewView nv;
    EventListener el(&nv);
    cout 

Следует обратить внимание что на этот момент к событию ColorChanged присоединены два метода - это NewView::OnColorChanged() и EventListener::React(). В этом можно убедиться, изменив значение цвета методом SetColor. В результате будут вызваны две вышеупомянутые функции. Далее объектом EventListener производится отсоединение своего метода от события. Повторно вызвав метод SetColor убеждаемся, что на событие реагирует только один метод - NewView::OnColorChange():

    el.Detach();
    cout 


Последние замечания

Архив, который идёт вместе со статьёй содержит в себе исходный код всех разобранных примеров. Для удобства также поставляются файлы проектов для Visual Studio.Net 2002. Виду отсутствия Visual C++ 6.0 программы на этом компиляторе не тестировались.

Если Вы хотите использовать классы Delegate и Event в своих проектах, подключите к ним файлы Delegate.h, Event.h, EventBase.h и EventBase.cpp.

Скачать все примеры в архиве

По всем вопросам и предложениям обращаться по адресу: oleg_s_@rambler.ru

Оставить комментарий

Комментарий:
можно использовать BB-коды
Максимальная длина комментария - 4000 символов.
 

Комментарии

1.
100.0M
05 апреля 2020 года
xabik xabik
0 / / 05.04.2020
Мне нравитсяМне не нравится
5 апреля 2020, 23:01:55
Если уж вы копипастите, делайте это полностью https://www.dotfix.net/doc/cpp_events.htm
2.
100.0M
05 апреля 2020 года
xabik xabik
0 / / 05.04.2020
+1 / -0
Мне нравитсяМне не нравится
5 апреля 2020, 22:52:45
Этот класс содержит в сбе частную переменную
делегат.
И где объявление этой частной переменной? Нужно разбираться помимо идеи ещё и в коде автора?
3.
100.0M
05 апреля 2020 года
xabik xabik
0 / / 05.04.2020
Мне нравитсяМне не нравится
5 апреля 2020, 22:49:18
<b><i>Этот класс содержит в сбе частную переменную
делегат.</i></b> И где объявление этой частной переменной? Нужно разбираться помимо идеи ещё и в коде автора?
4.
20K
13 июня 2007 года
#Monster#
20 / / 13.06.2007
+4 / -2
Мне нравитсяМне не нравится
2 декабря 2007, 00:34:23
мн.. интересно #include &ltiostream> это опечатка или суппер программерская фишка какя-нить=))
5.
Аноним
+2 / -2
Мне нравитсяМне не нравится
17 июля 2005, 12:32:40
Класс не работает со статическими функциями-членами
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог