Здравствуйте, eao197, Вы писали:
E>Я понял, что ты говоришь про указатели на методы класса.
Это радует.
E>Один из use case в которых я использую указатели на методы -- это реализация в объекте-классе чего-то типа конечного автомата. Наружу такой объект предоставляет, например, один метод: receive_event( event_t event ), а внутри себя должен выполнить действия в зависимости от состояния объекта. Решение в лоб выглядело бы так:
Знашь, я заметил за тобой одну проблему которая все время убивает дискуссию. Ты излишне переусложняешь свои примеры.
Какие-то конечные автоматы... Надо выделить основную сущьность и показать ее суть, а не наворачивать огромные примеры в которых сложно разобраться (а точнее очень не хочется разбираться).
Твой пример изумительно решается как на на обычных указателях на глобальны функции (не методы класса), так и на делегатах. Вот реализация на делегатах:
delegate void EventHandler(event_t event);
delegate void MyEventHandler(int eventId);
class some_class
{
public some_class() { _eventHandler = state_one_handler; }
MyEventHandler _eventHandler;
void receive_event(int eventId)
{
_eventHandler(eventId);
}
void state_one_handler(int eventId)
{
if (eventId == 1)
do_something();
else if (eventId == 2)
// Для переключения состояния просто меняем указатель на текущий обработчик.
_eventHandler = state_two_handler;
}
void state_two_handler(int eventId)
{
// ...
}
void do_something() { }
}
Основная суть здесь использование указателя на метод в качестве ссылки на алгоритм.
Так вот, твой случай частный, так как ссылка нужна в рамках класса. И как частный случай он легко реализуется на делегатах. Но твой случай можно усложнить введя требование возможности передачи управления в любой объект, ане только этот же или даже в объекты этого же типа. И тут делегаты опять таки дают чистое и красивое решение, а указатели на методы класса пасуют.
Проблема тут в черезмерной заботе о контроле типов. Страуструпу показалось, что если дать программистам возможность вызыать любой метод с некоторой сигнатурой, то появится куча ошибок. Но это ложное предположение.
Все что нужно сделать, это убрать этот лишний контроль. Но теперь уже в дело вступили амбиции. Убрать это ограничение значит признать собственную ошибку. А вот это-то похоже Страуструп делать очень не хочет. Не мог же он ошибиться в дизайне?
E>А можно было бы пойти еще дальше. Скажем завести отдельный map< event_t, (some_class_t::*)() > для каждого состояния. И занести в эти map-ы указатели на методы, которые должны вызываться в конкретном состоянии по конкретному событию. Тогда смена состояния будет означать просто смену указателя на нужный map. А работа метода receive_event -- поиск указателя в map-е по идентификатору события и вызов найденого обработчика.
Ага. Очень хороший прием. И в Шарпе он используется на право и на лево. Причем выглядит очень компактно и красиво. В сочетании с рефлекшоном он позволяет просто таки творить чудеса. Вот, например, простенький пример крэкера сообщений написанного на C#
Крекер Windows-сообщений в AOP-стилеАвтор: VladD2
Дата: 11.05.04
. Благодаря мапу делегатов, рефлекшону и атрибутам обработку сообщений виндовс получилось сделать декларативным:
// Костанты-идентификаторы сообщений. Можно надыбать на www.codeproject.com или в winuser.h
const int WM_LBUTTONDOWN = 0x0201;
const int WM_RBUTTONDOWN = 0x0204;
// Обработчики сообщений. Атрибутом WinMsg с ними ассоциируются сами сообщения.
[WinMsg(WM_RBUTTONDOWN)]
bool OnRButtonDown(ref Message msg)
{
BackColor = Color.Blue;
return true;
}
[WinMsg(WM_LBUTTONDOWN)]
bool OnLButtonDown(ref Message msg)
{
BackColor = Color.Red;
return true;
}
А вот пример из редактора кода. Он довольно длинный, так что сначало опишу его применение, а потом приведу код.
Этот пример показывает как можно довести обработку клавиатурный ввод до полной декларативности и позволить задавать их не в программе, а в конфигурационном файле.
Итак, в контроле нужно добавить следующий код:
/// <summary>
/// Ассоциативный массив ключем котрого является клавиатурное сокращения,
/// а значением обработчик выполняемый при нажатии этого клавиатурного
/// сокращения.
/// </summary>
private KeyboardShortcutsMap _keyboardMap;
...
// В конструкторе:
_keyboardMap = new KeyboardShortcutsMap(this);
...
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyValue >= 16 && e.KeyValue <= 18) // Alt, Control, Shift
return;
// Получаем обработчик ассоциирванный с текущим клавиатурным сокращением.
KeyboardShortcutsMap.KeyHandler keyDownHandler = _keyboardMap[e.KeyData];
if (keyDownHandler != null)
{
keyDownHandler();
e.Handled = true;
e.SuppressKeyPress = true;
}
else
base.OnKeyDown(e);
}
А вот так выглядит конфигурационный файл:
<?xml version="1.0" encoding="utf-8" ?>
<Shortcuts>
<!-- Навигация по тексту -->
<Shortcut Key="Up" Action="CaretVirtualLineUp"/>
<Shortcut Key="Down" Action="CaretVirtualLineDown"/>
<Shortcut Key="Shift | Up" Action="CaretVirtualLineUpExtend"/>
<Shortcut Key="Shift | Down" Action="CaretVirtualLineDownExtend"/>
<Shortcut Key="Home" Action="CaretVirtualLineHome"/>
<Shortcut Key="End" Action="CaretVirtualLineEnd"/>
<Shortcut Key="Shift | Home" Action="CaretVirtualLineHomeExtend"/>
<Shortcut Key="Shift | End" Action="CaretVirtualLineEndExtend"/>
<Shortcut Key="Left" Action="CaretLeft"/>
<Shortcut Key="Control | Left" Action="CaretWordLeft"/>
<Shortcut Key="Control | Right" Action="CaretWordRight"/>
<Shortcut Key="Shift | Control | Left" Action="CaretWordLeftExtend"/>
...
<!-- Клипборд -->
<Shortcut Key="Control | C" Action="Copy"/>
<Shortcut Key="Control | Insert" Action="Copy"/>
<Shortcut Key="Control | V" Action="Paste"/>
<Shortcut Key="Shift | Insert" Action="Paste"/>
<Shortcut Key="Control | X" Action="Cut"/>
<Shortcut Key="Shift | Delete" Action="Cut"/>
...
</Shortcuts>
А вот так выглядит реализация:
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Diagnostics;
using System.Windows.Forms;
using System.Reflection;
namespace Rsdn.Editor
{
/// <summary>
/// Хэлпер-класс считывающий информацию о клавиатурных сокращениях из
/// файла настроек или ресурсов и преобразующий его во внутренний словарь.
/// После инициализации можно пользоваться индексером объекта чтобы
/// получать делегаты-обработчики клавиатурных событий.
/// Файл должен называться KeyboardShortcutsMap.xml.
/// Файл с описанием клавиатурных сокращений может находиться
/// в каталоге %APPDATA%\Rsdn.Editor иди каталоге где располагаться сборка
/// в котророй находится элемент управления.
/// Если файл не найден в одном из этих каталогов, то описание
/// клавиатурных сокращений берется из ресурсов.
/// </summary>
internal class KeyboardShortcutsMap
{
/// <summary>
/// Тип метода вызываемого при клавиатурном сокращении.
/// </summary>
public delegate void KeyHandler();
const string MapFileName = "KeyboardShortcutsMap.xml";
public KeyboardShortcutsMap(Control view)
{
using (XmlReader reader = OpenReader())
{
if (!reader.Read() || reader.Name != "xml")
throw new ApplicationException("Неверный формат файла KeyboardShortcutsMap.xml");
while (reader.Read() && string.IsNullOrEmpty(reader.Name))
;
if (reader.Name != "Shortcuts")
throw new ApplicationException("Неверный формат файла KeyboardShortcutsMap.xml");
Type keyHandlerType = typeof(KeyHandler);
object[] args = new object[0];
StringBuilder errors = new StringBuilder(); // сообщения об ошибках
while (reader.Read())
{
if (reader.Name == "Shortcut")
{
string key = null;
string action = null;
try
{
key = reader.GetAttribute("Key"); // читаем ключь
// читаем имя делегата
action = reader.GetAttribute("Action");
// Получам клавиатурное сокращение плюс делегат и инициализируем
// этими значениями внутренний словарь.
SetKeyboardHandler(ParseKeys(key),
(KeyHandler)KeyHandler.CreateDelegate(
keyHandlerType, view, action, false, true));
}
catch (Exception ex)
{
errors.AppendLine("Can't add key '" + key + "' with action '"
+ action + "'. " + ex.Message);
}
}
}
if (errors.Length > 0)
MessageBox.Show(view.FindForm(),
"При считывании файла с клавиатурными сокращениями были обнаружены "
+ "следующие ошибки: " + Environment.NewLine + errors, Utils.AppName,
MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
/// <summary>
/// Открывает XML-файл содержащий описание клавиатурных сокращений
/// на чтение.
/// </summary>
private static XmlReader OpenReader()
{
XmlReader reader;
string path = Environment.GetFolderPath(
Environment.SpecialFolder.ApplicationData);
path = Path.Combine(Path.Combine(path, Utils.AppName), MapFileName);
// Если файл с клавиатурными настройками существует в папке "Application Data"
// (%APPDATA%), или в папке где расположена DLL-а, то читаем настройки из него.
// Иначе чиатаем настройки из файла сохраненного при компиляции в ресурсах.
if (File.Exists(path))
reader = XmlReader.Create(path);
else if (File.Exists(path = Path.Combine(Utils.GetModulePath(), MapFileName)))
reader = XmlReader.Create(path);
else
reader = XmlReader.Create(new StringReader(
Properties.Resources.KeyboardShortcutsMap));
return reader;
}
/// <summary>
/// Преобразует строку содержащую имена клавишь в Keys.
/// Имена клавишь могут объеденяться по или (знаком "|").
/// </summary>
/// <example>"Shift | Control | Right"</example>
/// <param name="key">Клавиатурное сокращение в виде строки.</param>
private static Keys ParseKeys(string key)
{
Keys keys;
// Разбиваем ключь на отдельные значения
string[] keyStrs = key.Split('|');
keys = 0;
foreach (string value in keyStrs)
keys |= (Keys)Enum.Parse(typeof(Keys), value);
return keys;
}
/// <summary>
/// Возвращает делегат-обработчик события соотвествующий клавиатурному
/// сокращению или null.
/// </summary>
/// <param name="shortcut">Клавиатурное сокращение.</param>
/// <returns>Делегат обработчки.</returns>
public KeyHandler this[Keys shortcut]
{
get
{
KeyHandler keyHandler;
if (_kbdMap.TryGetValue(shortcut, out keyHandler))
return keyHandler;
return null;
}
}
/// <summary>
/// Вспомогательный метод позволяющий пуростить инициализацию _kbdMap.
/// Ассоциирует клавиатурное сокращение и его обработчик.
/// </summary>
/// <param name="keyData">Клавиатурное сокращение.</param>
/// <param name="keyHandler">Обработчик.</param>
private void SetKeyboardHandler(Keys keyData, KeyHandler keyHandler)
{
if (_kbdMap.ContainsKey(keyData))
Trace.WriteLine("The keyboard shortcut '" + keyData + "' be found twice.");
_kbdMap[keyData] = keyHandler;
}
/// <summary>
/// Ассоциативный массив ключем котрого является клавиатурное сокращения,
/// а значением обработчик выполняемый при нажатии этого клавиатурного
/// сокращения.
/// </summary>
private Dictionary<Keys, KeyHandler> _kbdMap =
new Dictionary<Keys, KeyHandler>();
}
}
Теперь зададимся впоросом, в чем главное отличие этих примеров от твоего (если не смотреть на применение рефлекшона и других компонентных технологий)? Так вот главное отличие, что класс с реализацией фичи, и класс предоставляющий список функций — это два разных класса! Вот этого и не позволяют сделать указатели на методы класса в С++. А именно это применение самое желанное.
Так что указатели на методы класса в С++ — это всего лишь досадное недоразумение. Создание частного случая вместо реализации общего.
Ну, а все эти сигналы, бинды, фаншоны из С++ не боллее чем попытка обойти достадные ограничения за счет возможностей метапрограммирования и нехилых изысков.
E><...Эмоциональное описание того, что тебе кажется неправильным поскипано...>
E>Твоя точка зрения понятна. Но, имхо, она не правильная, а я согласен со Страуструпом.
Жаль, что не смог убедить. Но спасибо хотя бы за то, что я не услышал от тебя слов вроде тех что рядом излил ПК.
VD>>Какое отношение этот рассказ имеет к вопросу? Повторю еще раз цитаты:
VD>>>>>Да, не понимаю. Сборка это набор байт. Всегда можно скачать...
E>>>>Нет. Далеко не всегда.
VD>>>И когда нельзя?
VD>>>Раскрывай.
E>Влад, вот ты часто говоришь, что занимаешься интеропом. Но ты представляешь себе реальные прикладные протоколы, которые используются для организации взаимодействия нескольких удаленных машин? Вот скажем обмениваются информацией о транзакциях два банковких сервера. Или несколько машин в кластере обмениваются результатами вычислений. Или даже когда браузер обращается к web-серверу за страничкой. Все они ожидают получить данные, именно данные, а не код, который эти данные может распарсить. И проблемы начинаются, когда версия данных на одной стороне начинает разсогласовываться с тем, что ожидает другая сторона.
Ясно. Прямого ответа на вопрос я не получу. Ну, да понятно почему...
VD>>Что же до сериализации, то это проблемы С++ в ремоутинге и КОМ-е они решаются.
E>Насколько я знаю, там все решается на уровне жестко определенных интерфейсов. Причем не только в КОМ-е, но и в Corba. Как только интерфейсы перестают удовлетворять потребностям приходится вводить новые интерфейсы. Затем новые. Затем опять новые и т.д.
И да, и нет. Более вреный ответ будет звучать так: там все решается наличием исчерпывающих метаданных в рантайме. Если есть метаинформация, то проблемы вроде маршалинга и сериализации разрешимы. Проблема С++ в том что его авторы вообще не задумывались о метаинформации в программе.
E>А вот в Asn1 было введено понятие "extension points" -- это такие места в спецификации протокола, где говорится -- вот в этом месте в будущем может что-то появиться.
Очень смахивает на введение лишних параметров на всякий случай.
Мне кажется это очень не верное решение. Лучше не пытаться угадать что будет в будущем, а просто позволить читать информацию о том, что есть в настоящем.
E>Вот я и спрашивал, поддерживает ли BinaryFormatter в .Net подобные фишки. И все мои примеры как раз были об этом.
У тебя есть некоторое представление о том, ка должен быть реализован BinaryFormatter если для его реализации использовать С++ и привычные тебе идиомы/паттерны. Но это не вереный подход. Для понимания того что такое BinaryFormatter и как он устроен нужно понять идиомы и паттерны дотнета и отказаться от взгляда на мир с точки зрения С++.
В общем, проще говорить о возможностях. Сформулируй какие возможности ты хотел бы видеть от BinaryFormatter и я тебе скажу реализованы ли они или нет.
E>Понятно. Только, если тебе интересно, то выбери время и сходи. А нет, так я не вижу смысла копипастить сюда несколько полтора десятка килобайт описаний.
Если скопировать только суть, то смысл очень даже есть.
E>Потому что конкретный прикладной протокол придется заморачивать не только на передачу прикладных данных, но и на передачу кода.
Это можно сделать введя очень простенький компонент. В его задачи будет входить контроль наличия сборки (что в дотнете делается простым подключением на специальное событие) и подкачку нужной сборки если сборки еще нет.
Безусловно это дополнительный код, но к протоколу он отношения не имеет. Ну, и не так уж он и сложен.
E>Кроме того, если речь идет о банковских системах, то на 100% могу утверждать, что пробить через безопасность банка решение о динамической подгрузке на банковский сервер извне какого-то кода будет архисложно.
Это если люди отвечающие за решение совсем дремучи. Все проблемы безопасности решаются. Копировать можно только публичные интерфейсы.
E>Вот именно. Но я говорю о случаях, когда сериализация/десериализация на разных сторонах делается по разным версиям описания схемы данных.
Звучит не разумно.
VD>> Ну, и всегда можно сделать простенькое решение которое сможет копировать нужные интерфесные сборки в автомате. Итго — это не языковая проблема. Это проблема дизайна.
E>Именно дизайна. И попытаться решить нетривиальную проблему дизайна простенькими средствами языка может быть слишком сложно.
Ты погряз в догмах и проблемах С++. То что ты называшь "нетривиальной проблемой дизайна" на самом деле является обычной пробемой дизайна довольно не сложно решаемой в компонентных средах.
... << RSDN@Home 1.1.4 beta 7 rev. 466>>