Для решения задачи обеспечения работы undo/redo в приложении хочу четко детектировать какое обращение к объекту происходит: "только для чтения" или [возможно] "для целей модификации", например:
object* o;
...
o->ConstMethod(); // вызов константного метода
o->UnConstMethod1(arg); // вызов НЕконстантного метода
o->UnConstMethod2(arg1, arg2); // вызов другого НЕконстантного метода
...
Для этого ввел смарт-поинтер со следующей семантикой:
class object;
class ptr
{
public :
object* obj;
ptr(object* o);
// НЕконстантный оператор. Если он вызван, считается что будет произведена модицифирующая операция.
// Помечаем объект для последующего сохранения в Undo|Redo.
object* operator -> () { CheckObjForUndo(obj); return obj; }
// Константный оператор. Объект не модифицируется при этом, соответственно никаких пометок не производится.const object* operator -> () const { return obj; }
...
};
использование предполагал такое:
object* o;
...
ptr p(o);
...
p->ConstMethod(); // вызов константного метода
p->UnConstMethod1(arg); // вызов НЕконстантного метода
p->UnConstMethod2(arg1, arg2); // вызов другого НЕконстантного метода
При этом рассчитывал что компилятор сможет определить какой именно operator -> следует подставлять в то или иное выражение. К сожалению это не работает. Всегда вызывается неконстантный operator ->.
Может кто укажет на мои ошибки или предложит иное решение проблемы?
Re: разделение константного и неконстантного доступа
Здравствуйте, PaNov, Вы писали:
PN>... PN>При этом рассчитывал что компилятор сможет определить какой именно operator -> следует подставлять в то или иное выражение. К сожалению это не работает. Всегда вызывается неконстантный operator ->.
PN>Может кто укажет на мои ошибки или предложит иное решение проблемы?
Константный метод будет вызываться у константного обекта. Т.е. чтобы опросить объект, можно сделать так:
class Foo
{
public:
void bar() {}
void bar() const {}
};
Foo fooObj;
const Foo& constFooObj = fooObj;
constFooObj.bar(); // вот здесь всегда вызовется константная bar()
fooObj.bar(); // а здесь - всегда неконстантная.
А ти, москалику, вже приїхав (с)
Re[2]: разделение константного и неконстантного доступа
Здравствуйте, PaNov, Вы писали:
PN>Может кто укажет на мои ошибки или предложит иное решение проблемы?
С таким требованием к синтаксису вряд ли получится решить проблему. Оператор -> ничего не знает о своей правой части.
Можно сделать через более неприятный синтаксис:
o.callmethod(&object::ConstMethod)(); // вызов константного метода
o.callmethod(&object::UnConstMethod1)(arg); // вызов НЕконстантного метода
o.callmethod(&object::UnConstMethod2)(arg1, arg2); // вызов другого НЕконстантного метода
/* или вместо callmethod - оператор ->* или [] или () */
Для вывода типа функтора, возвращаемого из callmethod и определения кнстантности параметра можно взять boost::function_types , для типа функтора — boost::function, для его формирования — boost::bind
Русский военный корабль идёт ко дну!
Re: разделение константного и неконстантного доступа
Здравствуйте, PaNov, Вы писали:
PN>При этом рассчитывал что компилятор сможет определить какой именно operator -> следует подставлять в то или иное выражение. К сожалению это не работает. Всегда вызывается неконстантный operator ->.
PN>Может кто укажет на мои ошибки или предложит иное решение проблемы?
Можно оставить у умного указателя только константный operator->(), плюс сделать вспомогательную функцию или переменную для предоставления не константного доступа, тогда получится что-то типа такого:
obj->const_method();
obj.var->non_const_method();
// или как вариант:
obj.var()->non_const_method();
obj()->non_const_method();
Поскольку по-умолчанию даётся только константный доступ, то компилятор укажет на все места, где нужно добавить/забыли ".var".
Здравствуйте, remark, Вы писали:
R>Можно оставить у умного указателя только константный operator->(), плюс сделать вспомогательную функцию или переменную для предоставления не константного доступа, тогда получится что-то типа такого: R>Поскольку по-умолчанию даётся только константный доступ, то компилятор укажет на все места, где нужно добавить/забыли ".var".
Спасибо за ответ.
Такой вариант рассматривался как "последнее средство", если не найдётся более изящной семантики.
Re[2]: разделение константного и неконстантного доступа
Здравствуйте, Alexander G, Вы писали:
AG>С таким требованием к синтаксису вряд ли получится решить проблему. Оператор -> ничего не знает о своей правой части.
AG>Можно сделать через более неприятный синтаксис:
AG>Для вывода типа функтора, возвращаемого из callmethod и определения кнстантности параметра можно взять boost::function_types , для типа функтора — boost::function, для его формирования — boost::bind
Спасибо за ответ, но, к сожалению, Ваше предложение неприменимо в нашей ситуации.
Дело в том что:
— object — является частью немаленького фреймворка (CAD система), от него уже сейчас имеется четыре десятка наследников разной крупности;
— написано много прикладного кода кода работающего с объектами.
Применять чуднУю семантику — обойдётся крайне дорого как с точки зрения разовой переработки кода (что можно еще было бы провернуть), так и для дальнейшего его развития (что недопустимо).
Так что, мы пока остаёмся в поиске
Re: разделение константного и неконстантного доступа
Здравствуйте, PaNov, Вы писали:
PN>Приветствую всех разработчиков.
PN>Для решения задачи обеспечения работы undo/redo в приложении хочу четко детектировать какое обращение к объекту происходит: "только для чтения" или [возможно] "для целей модификации", например:
PN>Может кто укажет на мои ошибки или предложит иное решение проблемы?
Немного не из той оперы, но если речь идет о Win32, то можно иметь два указателя на объект — один с правами чтение/запись, другой — только чтение. При попытке обратиться для модификации по указателю "только чтение" гарантированно получишь access violation.
Правда, при этом придется писать свой собственный аллокатор.
Здравствуйте, PaNov, Вы писали:
PN>Для решения задачи обеспечения работы undo/redo в приложении хочу четко детектировать какое обращение к объекту происходит: "только для чтения" или [возможно] "для целей модификации", например:
А лепо ли создавать точку отката на каждый чих?
А если нужно выполнить составную операцию, разрывать которую точками отката нельзя?
Вариант, когда права продвигаются, кажется наиболее логичным.
Т.е. напрямую указатель — на константу, а хочешь получить неконстанту — вызываешь метод, возвращающий указатель на неконстанту (и попутно создающий точку). Дальше с указателем на неконстанту можно делать что угодно, точка создана не будет.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Перекуём баги на фичи!
Re: IMHO, надо немного другой принцип разделения юзать...
Здравствуйте, PaNov, Вы писали:
PN>Для решения задачи обеспечения работы undo/redo в приложении хочу четко детектировать какое обращение к объекту происходит: "только для чтения" или [возможно] "для целей модификации", например:
IMHO, уже в этом месте есть косяк в архитектуре. Кодт совершенно прав. Обычно от системы Undo и дял системы логгирования нужено намного более тонкое управление, чем доступ такой или доступ сякой.
Так что совсем автоматом сделать это не получится.
Можно попробовать сделать полуавтоматом.
Полуавтомат будет такой, что заводишь специальный умный указатель CObjectModificationSession, который обладает таким свойством, что когда его сощдаёшь, захватываешь object на изменения. Потом, пока существует хотя бы одна копия -- производишь изменения, потом, когда этот объект разрушаешь -- сессия изменений завершается, в undo-стек записывается очережная запись и всё такое.
Соответсвенно можно у какого-то другого указателя и владельца object'ом иметь метод, который возвращает CObjectModificationSession, и в параметрах которого, при ниебходимости, задаются особенности групповой опперации и её логгирования или помещения в тек undo. Например, тот факт, что стек undo на это операции прерывается...
Можно, так же, добавить в CObjectModificationSession метод, явно принимающий групповую операцию, чтобы не ждать деструктора. Ну и много других полезных сервисов реализовать, например транзакционность...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[2]: разделение константного и неконстантного доступа
Здравствуйте, Кодт, Вы писали:
К>А лепо ли создавать точку отката на каждый чих? К>А если нужно выполнить составную операцию, разрывать которую точками отката нельзя?
Обсуждаемую технику я предполагал использовать исключительно для ПОМЕТКИ объекта о том что он модифицирован.
Принятие решение о сохранении объекта в undo|redo принимается позже, за границами прикладной функции. Например:
Механика примерно такая:
— при вызове UndoStart() всем объектам сбрасывается пометка модифицированности.
— при вызове UndoFinish() эта пометки анализируются, и те объекты которые были помечены как изменившиеся — падают в стеки undo|redo (как одна запись — "Сдвиг выбранных фигур").
Функции UndoStart/UndoFinish — обеспечивают границы прикладной функции. Эти границы может определить только прикладной программист.
Между UndoStart/UndoFinish может производиться сколь угодно сложная обработка множества объектов (как на чтение, так и на запись).
В ряде случаев (в нашей системе) даже сложно четко определить объектов — участников прикладной функции, не говоря уже о том чтобы прикладник сам вычислял модифицированные объекты. Связано это в первую очередь с активным использованием механизма — оповещение/подписка. Соответственно требуется прозрачный для прикладника механизм — могущий отделить использование объекта на чтение от использования на запись.
Подводя итоги — сабж имел своей целью выявить приемлемую семантическую форму для механизма пометки объекта при изменениях.
Задача пока остаётся актуальной.
Re[2]: IMHO, надо немного другой принцип разделения юзать...
Здравствуйте, Erop, Вы писали:
E>IMHO, уже в этом месте есть косяк в архитектуре. Кодт совершенно прав. Обычно от системы Undo и дял системы логгирования нужено намного более тонкое управление, чем доступ такой или доступ сякой.
E>Так что совсем автоматом сделать это не получится. E>Можно попробовать сделать полуавтоматом.
Спасибо за ответ.
Да, предполагаемое решение примерно так и должно выглядеть, как Вы это описываете. Выше в ветке я ответил
Кодт'у.
Однако в вашем решении Вы исходите из того — что прикладник знает какой объект/множество объектов будет изменено в процессе выполнения прикладной функции.
Мне же хотелось бы автоматом получить информацию том, какие объекты изменятся, т.к. прикладник в нашей системе это не может контролировать полностью. Связано это с системой оповещения/подписки, когда вызванное пользователем изменение одного объекта может иницировать согласование состояния других объектов (с проставлением соответствующих пометок о модификации) по отношению к изменившемуся объекту, и как следствие этого — каскадное (это в лучшем случае, в общем случае — система представляет собой ориентированный граф) согласование объектов. Связи при этом отследить крайне сложно, следовательно решение получится ненадежным.
Re[3]: Возможно, лучше всего вообще отказаться от неконстант
Тут вроде бы принято на "ты".
PN>Однако в вашем решении Вы исходите из того — что прикладник знает какой объект/множество объектов будет изменено в процессе выполнения прикладной функции.
Нет. Нисколько.
PN>Мне же хотелось бы автоматом получить информацию том, какие объекты изменятся, т.к. прикладник в нашей системе это не может контролировать полностью.
Если я верно понял о чём речь, то так сделать не получится. Если объекты умеют дёргать друг-друга помимо нашего умного указателя/сессии модификации, то всё равно часть изменений неконтролируемо пройдёт мимо нас.
А если все эти нотификации и идут через нашу машинку, то ничто не мешает, например, сделать так, что при открытой сессии модификации она запоминается в глобальной переменной, или в корневом объекте, или ещё где. После этого нотификации шлются через неё. А если нотификация попытается позваться в момент, когда сессия модификации закрыта, то тогда это assert.
Но это только одна из возможных схем...
PN>Связано это с системой оповещения/подписки, когда вызванное пользователем изменение одного объекта может иницировать согласование состояния других объектов (с проставлением соответствующих пометок о модификации) по отношению к изменившемуся объекту, и как следствие этого — каскадное (это в лучшем случае, в общем случае — система представляет собой ориентированный граф) согласование объектов. Связи при этом отследить крайне сложно, следовательно решение получится ненадежным.
Эх, мне кажется, что тебе надо подробнее рассказать что за библиотека и что за задача.
Потому как в том, что ты описываешь, я вижу несколько проблем.
1) Мы предполагаем, что все неконстантныне методы, в том числе и реакции на уведомления, меняют объекты. IMHO, это слишком сильное утверждение, которое будет приводить к преувеличению объёма возникших изменений.
2) Ничто не гарантирует отсутствие у объектов прямых ссылок друг на друга мимо нашей машинки, что легко и непринуждённо позволит её обманывать
3) Чтобы сделать на этом undo, нам придётся научиться массово и задёшево копировать и разрушать наши объекты.
IMHO, это всё надо решать в комплексе. Скажем, если у тебя есть какая-то сильно связанная-перевязанная между собой структура данных, то можно пойти таким путём, что
1) Научиться быстро создавать и разрушать запчасти. Например при помощи блочного аллокатора.
2) Научиться управлять временем жизни её частей. Один и простых способов -- COW + подсчёт ссылок.
3) Переписать код изменения частей так, чтобы вместо того, чтобы менять какую-то часть, всегда создавали изменённую копию.
4) Теперь, при создании UNDO-точки, надо всего лишь запомнить указатели на корневые объекты этой структуры данных. Потом заняться её изменением (в смысле порождением новых, изменённых частей)
В результате у вас будут доступны сразу все версии структуры данных
В качестве развития такой идеи, можно сделать так, что ссылки между запчастями структуры данных, будут осуществляться не указателями, а специальными хендлами, или умными указателями, поддерживающими версионность. Ну, типа 127 версия такого-то объекта. И на undo-точку можно будет запоминать версии всех объектов на момент начала операции. А в конце каждой операции, можно будет ещё и разрушать все объекты промежуточных версий, кстати...
Короче надо рассказать задачу, чтобы было понятно о чём речь...
Может быть лучше вообще отказаться от неконстантных методов?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском