Доброго дня.
Раньше как-то сильно не задумывался и негласно для себя считал, что const-спецификатор метода гарантирует неизменность полей объекта, из которого этот метод вызывается. А вот когда начал работать с сигнал-слот-архитектурой, увидел, что это, вообще говоря, не так:
class C_B;
////////////////////////////
//// C_A ////
////////////////////////////class C_A
{
C_B* m_pB;
int m_x;
public:
//------------------------------------------void set_pB(C_B* pB){m_pB = pB;}
//------------------------------------------void set_x(int x) {m_x = x;}
//------------------------------------------void foo_A(void)const;// несмотря на const, вызов этой функции может изменить значение поля m_x
//------------------------------------------
};
////////////////////////////
////////////////////////////
//// C_B ////
////////////////////////////class C_B
{
C_A* m_pA;
public:
//------------------------------------------
C_B(C_A* pA):m_pA(pA){}
//------------------------------------------void foo_B(void){ m_pA->set_x(10);}
//------------------------------------------
};
////////////////////////////
//------------------------------------------void C_A::foo_A(void)const
{
m_pB->foo_B();
}
//------------------------------------------void main(void)
{
C_A A;
C_B B(&A);
A.set_pB(&B);
A.set_x(0);
A.foo_A();
}
Потому встал вопрос, есть ли в других языках или в новом стандарте с++ что-нибудь наподобие "class const — метода", который делает
константными ЛЮБЫЕ this данного класса в своей области видимости, и тем самым предотвращает всякое изменение любого (а значит и заданного)объекта данного класса?
Это понадобилось, в частности, для того, чтобы на этапе компиляции отлавливать зацикливания. Например,
если foo_A воспринимаеть как сигнал о модификации объекта, к которому приконнекчен слот foo_B, то хотелось бы иметь гарантиии, что вызов этого слота ен приведет к повторному изменению объекта и повторному вызову сигнала (тем самым входя в цикл).
Re: "class const - метод" - можно ли организовать?
Здравствуйте, _hum_, Вы писали:
__> Раньше как-то сильно не задумывался и негласно для себя считал, что const-спецификатор метода гарантирует неизменность полей объекта, из которого этот метод вызывается.
Я, как сишник, для себя считаю, что const-спецификатор есть в первую очередь указание оптимизатору, что некоторый регион памяти не изменяется в течение жизни указателя. И если он изменяется по какой-то причине, то получаем UB. Истина математика всегда делать самые слабые утверждения А как оно в стандарте
Реализовать твоё поведение вряд ли возможно в принципе. Потому как на каждую операцию записи нам необходимо доказать, что невозможно изменение содержимого любого константного указателя. Что нетривиально.
Re[2]: "class const - метод" - можно ли организовать?
Здравствуйте, VTT, Вы писали:
VTT>У вас тут const-ность нарушается из-за наличия в классе не const ссылки на самого себя. Этот пример можно упростить до VTT>
да, спасибо, так нагляднее.
VTT>Если хочется гарантий, то можно делать immutable объекты.
вы не совсем тогда поняли — я хочу гарантию, что ВЫЗОВ ДАННОГО МЕТОДА не приведет к изменению данных, а не что эти данные ВОВСЕ не должны меняться.
и реализация этого вроде бы не сложная — компилятор должен все this-указатели данного класса, которые попадают в область видимости данного метода, считать const this. тогда бы он смог выдать ошибку в вашем примере, типа:
"m_p_non_const->m_x = x : использование неконстантного указателя на класс t_Something внутри class const метода"
Re[2]: "class const - метод" - можно ли организовать?
Здравствуйте, Mystic, Вы писали:
M>Здравствуйте, _hum_, Вы писали:
__>> Раньше как-то сильно не задумывался и негласно для себя считал, что const-спецификатор метода гарантирует неизменность полей объекта, из которого этот метод вызывается.
M>Я, как сишник, для себя считаю, что const-спецификатор есть в первую очередь указание оптимизатору, что некоторый регион памяти не изменяется в течение жизни указателя. И если он изменяется по какой-то причине, то получаем UB. Истина математика всегда делать самые слабые утверждения А как оно в стандарте
так одно другого не исключает мы говорим компилятору (который включает в себя и синтаксический анализатор, и оптимизатор), как мы считаем, должен себя вести объект, а уже компилятор использует эту инфу для многих целей — и для отлавливания семантических ошибок (сказали, что будем использовать без изменения, и почему-то пишем изменяющий код) и для оптимизации.
M>Реализовать твоё поведение вряд ли возможно в принципе. Потому как на каждую операцию записи нам необходимо доказать, что невозможно изменение содержимого любого константного указателя. Что нетривиально.
вроде бы несложно (см. пост выше для VTT)
Re[3]: "class const - метод" - можно ли организовать?
__>вы не совсем тогда поняли — я хочу гарантию, что ВЫЗОВ ДАННОГО МЕТОДА не приведет к изменению данных, а не что эти данные ВОВСЕ не должны меняться. __>и реализация этого вроде бы не сложная — компилятор должен все this-указатели данного класса, которые попадают в область видимости данного метода, считать const this. тогда бы он смог выдать ошибку в вашем примере, типа: __>"m_p_non_const->m_x = x : использование неконстантного указателя на класс t_Something внутри class const метода"
Это может быть и некоторый другой класс, откуда компилятору знать, куда будет указывать m_p_non_const в момент вызова метода?
Re[4]: "class const - метод" - можно ли организовать?
Здравствуйте, Mystic, Вы писали:
M>Здравствуйте, _hum_, Вы писали:
__>>вы не совсем тогда поняли — я хочу гарантию, что ВЫЗОВ ДАННОГО МЕТОДА не приведет к изменению данных, а не что эти данные ВОВСЕ не должны меняться. __>>и реализация этого вроде бы не сложная — компилятор должен все this-указатели данного класса, которые попадают в область видимости данного метода, считать const this. тогда бы он смог выдать ошибку в вашем примере, типа: __>>"m_p_non_const->m_x = x : использование неконстантного указателя на класс t_Something внутри class const метода"
M>Это может быть и некоторый другой класс, откуда компилятору знать, куда будет указывать m_p_non_const в момент вызова метода?
неважно, куда будет указывать. главное, какого он типа. а это определяется на этапе компиляции.
(речь не о динамических отлавливаниях изменений, когда, действительно, требуется рантайм-анализ указателей, а о статическом — мы просто тупо запрещаем в коде, попадающем в область видимости данного метода, менять что-то через указатели данного класса)
Re[3]: "class const - метод" - можно ли организовать?
Здравствуйте, _hum_, Вы писали:
__>вроде бы несложно (см. пост выше для VTT)
Например?
struct X
{
X * next;
void f() const { next->next = nullptr; }
};
void something(X * x);
int main()
{
X x;
something(&x); // Какими будут поля структуры X после выхода из метода?
x.f(); // Как понять, есть нарушение или нет?return 0;
}
#ifdef UB
void something(X * x)
{
x->next = x;
}
#else
void something(X * x)
{
x->next = new X();
}
#endif
Re[5]: "class const - метод" - можно ли организовать?
Здравствуйте, _hum_, Вы писали:
__>неважно, куда будет указывать. главное, какого он типа. а это определяется на этапе компиляции. __>(речь не о динамических отлавливаниях изменений, когда, действительно, требуется рантайм-анализ указателей, а о статическом — мы просто тупо запрещаем в коде, попадающем в область видимости данного метода, менять что-то через указатели данного класса)
Т. е. ты хочешь, чтобы некоторый superconst влиял на все экземпляры данного класса?
Re[5]: "class const - метод" - можно ли организовать?
Здравствуйте, _hum_, Вы писали:
__>(речь не о динамических отлавливаниях изменений, когда, действительно, требуется рантайм-анализ указателей, а о статическом — мы просто тупо запрещаем в коде, попадающем в область видимости данного метода, менять что-то через указатели данного класса)
Ну можно попробовать использовать не указатели, а специальный reference_wrapper класс, который бы давал доступ к изменяемому объекту только если он сам не const.
т.е. имел бы два метода (в отличие от стандартного)
T & get();
T const & get() const;
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
Re[6]: "class const - метод" - можно ли организовать?
Здравствуйте, Mystic, Вы писали:
M>Здравствуйте, _hum_, Вы писали:
__>>неважно, куда будет указывать. главное, какого он типа. а это определяется на этапе компиляции. __>>(речь не о динамических отлавливаниях изменений, когда, действительно, требуется рантайм-анализ указателей, а о статическом — мы просто тупо запрещаем в коде, попадающем в область видимости данного метода, менять что-то через указатели данного класса)
M>Т. е. ты хочешь, чтобы некоторый superconst влиял на все экземпляры данного класса?
ну, можно и так сказать (только не на сами экземпляры, а на указатели на эти экземпляры, и не на все указатели, а на те, что по коду попадают в область видимости данного superconst- метода)
Re[4]: "class const - метод" - можно ли организовать?
Здравствуйте, Mystic, Вы писали:
M>Здравствуйте, _hum_, Вы писали:
__>>вроде бы несложно (см. пост выше для VTT)
M>Например?
M>
M>struct X
M>{
M> X * next;
M> void f() const { next->next = nullptr; }
M>};
M>void something(X * x);
M>int main()
M>{
M> X x;
M> something(&x); // Какими будут поля структуры X после выхода из метода?
M> x.f(); // Как понять, есть нарушение или нет?
M> return 0;
M>}
M>#ifdef UB
M> void something(X * x)
M> {
x->>next = x;
M> }
M>#else
M> void something(X * x)
M> {
x->>next = new X();
M> }
M>#endif
M>
если под
void f() const { next->next = nullptr; }
понималось
void f() class_const { next->next = nullptr; }
то компилятор уже в этом месте не пропустит, ибо у вас в области видимости class_const-метода осуществляется изменение через указатель на данный класс.
иными словами, компилятор трактует это как:
Здравствуйте, _hum_, Вы писали:
__> Доброго дня. __> Раньше как-то сильно не задумывался и негласно для себя считал, что const-спецификатор метода гарантирует неизменность полей объекта, из которого этот метод вызывается. А вот когда начал работать с сигнал-слот-архитектурой, увидел, что это, вообще говоря, не так:
Авторы не захотели прописать mutable напротив поля m_x (или закладывались на какие-то компиляторы без поддержки оного), вот и делается такой "хак себя", когда весь класс неизменный, но определённое поле — эдакая temp-переменная, вроде как захваченная в контекст.
Здравствуйте, VTT, Вы писали:
VTT>Здравствуйте, _hum_, Вы писали:
__>>(речь не о динамических отлавливаниях изменений, когда, действительно, требуется рантайм-анализ указателей, а о статическом — мы просто тупо запрещаем в коде, попадающем в область видимости данного метода, менять что-то через указатели данного класса)
VTT>Ну можно попробовать использовать не указатели, а специальный reference_wrapper класс, который бы давал доступ к изменяемому объекту только если он сам не const. VTT>т.е. имел бы два метода (в отличие от стандартного) VTT>
VTT>T & get();
VTT>T const & get() const;
VTT>
а разве это может спасти от ситуации с моим первым примером, когда C_B не использует (ибо его никто не может обязать это делать) такой враппер для хранения ссылки на объект C_A?
Re[2]: "class const - метод" - можно ли организовать?
Здравствуйте, Mr.Delphist, Вы писали:
MD>Здравствуйте, _hum_, Вы писали:
__>> Доброго дня. __>> Раньше как-то сильно не задумывался и негласно для себя считал, что const-спецификатор метода гарантирует неизменность полей объекта, из которого этот метод вызывается. А вот когда начал работать с сигнал-слот-архитектурой, увидел, что это, вообще говоря, не так:
MD>Авторы не захотели прописать mutable напротив поля m_x (или закладывались на какие-то компиляторы без поддержки оного), вот и делается такой "хак себя", когда весь класс неизменный, но определённое поле — эдакая temp-переменная, вроде как захваченная в контекст.
MD>Собственно, ничего нового в явлении нет, идиома известна ещё со времён plain C, равно как и грабли ей сопутствующие http://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule
не совсем понял, о чем вы. мютабельность полей тут не при чем.
Re[7]: "class const - метод" - можно ли организовать?
Здравствуйте, VTT, Вы писали:
VTT>Ну если уж вы часть программы не контролируете, то никакой superconst тут не поможет.
нет, речь о другом — в самом классе C_A я могу проконтролировать, чтобы все указатели на себя использовались только через такие врапперы, но за пределами этого класса — нет. и глупо рассчитывать на то, что использовать объекты этого класса будут только через врапперы (ну разве только специально писать инструкцию пользования данным классом, что довольно дико).
а компилятор мог бы это автоматом все отслеживать и говорить, где что не так...
Re[9]: "class const - метод" - можно ли организовать?
Здравствуйте, _hum_, Вы писали:
__>а компилятор мог бы это автоматом все отслеживать и говорить, где что не так...
И как бы он мог это отслеживать? Информации о том, указывает ли указатель в классе C_B на тот же экземпляр класса C_A, что и this в методе foo_A у него в принципе нет. В приведенном примере это очевидно только из-за крайнего упрощения.
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
Re[10]: "class const - метод" - можно ли организовать?
Здравствуйте, VTT, Вы писали:
VTT>Здравствуйте, _hum_, Вы писали:
__>>а компилятор мог бы это автоматом все отслеживать и говорить, где что не так...
VTT>И как бы он мог это отслеживать? Информации о том, указывает ли указатель в классе C_B на тот же экземпляр класса C_A, что и this в методе foo_A у него в принципе нет. В приведенном примере это очевидно только из-за крайнего упрощения.
нет. и вы тоже не поняли значит. речь не о том, чтобы не модифицировать ЗАДАННЫЙ ЭКЗЕМПЛЯР, а о том, чтобы запретить модификацию ВСЕ ЭКЗЕМПЛЯРЫ, если код их модификации попадает "в нитку", которая запускается вызовом class_const-метода.
иными словами компилятор должен действовать так:
0) заводит стек функций
1) помещает в стек class_const-метод
2) пока стек не пуст, берет верхний элемент стека и для него анализирует тело функции:
2.1) все указатели типа C_A* трактует как const C_A* и выдает ошибку, если с помощью них производится попытка модификации;
2.2) если встретилась функция, то помещает в стек и переходит к 2);
2.3) делает pop для стека.
3) анализ завершен
(тут еще оговорки, конечно, для рекурсивных вызовов, но это тоже решаемо на этапе анализа — просто не надо добавлять в стек то, что уже раньше встречалось)
Re[11]: "class const - метод" - можно ли организовать?
Здравствуйте, _hum_, Вы писали:
__>нет. и вы тоже не поняли значит. речь не о том, чтобы не модифицировать ЗАДАННЫЙ ЭКЗЕМПЛЯР, а о том, чтобы запретить модификацию ВСЕ ЭКЗЕМПЛЯРЫ, если код их модификации попадает "в нитку", которая запускается вызовом class_const-метода.
__>иными словами компилятор должен действовать так:
__>0) заводит стек функций __>1) помещает в стек class_const-метод __>2) пока стек не пуст, берет верхний элемент стека и для него анализирует тело функции: __> 2.1) все указатели типа C_A* трактует как const C_A* и выдает ошибку, если с помощью них производится попытка модификации; __> 2.2) если встретилась функция, то помещает в стек и переходит к 2); __> 2.3) делает pop для стека. __>3) анализ завершен
__>(тут еще оговорки, конечно, для рекурсивных вызовов, но это тоже решаемо на этапе анализа — просто не надо добавлять в стек то, что уже раньше встречалось)
Запретить модификацию всех экземпляров можно в runtime — сделать в классе дополнительное статическое поле со счетчиком const обращений и кидать исключение, если при вызове не-const метода этот счетчик не 0.
А на этапе компиляции это сделать не получится, так как у компилятора может не быть реализации класса C_B (или это вообще голый интерфейс), и, соответственно, он не сможет определить наличие обращение к не-const экземплярам C_A в нем.
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
Re[12]: "class const - метод" - можно ли организовать?
Здравствуйте, VTT, Вы писали:
VTT>Здравствуйте, _hum_, Вы писали:
__>>нет. и вы тоже не поняли значит. речь не о том, чтобы не модифицировать ЗАДАННЫЙ ЭКЗЕМПЛЯР, а о том, чтобы запретить модификацию ВСЕ ЭКЗЕМПЛЯРЫ, если код их модификации попадает "в нитку", которая запускается вызовом class_const-метода.
__>>иными словами компилятор должен действовать так:
__>>0) заводит стек функций __>>1) помещает в стек class_const-метод __>>2) пока стек не пуст, берет верхний элемент стека и для него анализирует тело функции: __>> 2.1) все указатели типа C_A* трактует как const C_A* и выдает ошибку, если с помощью них производится попытка модификации; __>> 2.2) если встретилась функция, то помещает в стек и переходит к 2); __>> 2.3) делает pop для стека. __>>3) анализ завершен
__>>(тут еще оговорки, конечно, для рекурсивных вызовов, но это тоже решаемо на этапе анализа — просто не надо добавлять в стек то, что уже раньше встречалось)
VTT>Запретить модификацию всех экземпляров можно в runtime — сделать в классе дополнительное статическое поле со счетчиком const обращений и кидать исключение, если при вызове не-const метода этот счетчик не 0.
неее, в рантайме это не дело.
VTT>А на этапе компиляции это сделать не получится, так как у компилятора может не быть реализации класса C_B (или это вообще голый интерфейс), и, соответственно, он не сможет определить наличие обращение к не-const экземплярам C_A в нем.
не совсем понял, в каком месте (и в какой ситуации) не пройдет описанный мною выше алгоритм?
Re: "class const - метод" - можно ли организовать?
Здравствуйте, _hum_, Вы писали:
__>Потому встал вопрос, есть ли в других языках или в новом стандарте с++ что-нибудь наподобие "class const — метода", который делает __>константными ЛЮБЫЕ this данного класса в своей области видимости, и тем самым предотвращает всякое изменение любого (а значит и заданного)объекта данного класса?
Может и есть, но в С++ это не возможно, так как можно взять указатель на память, где лежит данное, привести его void*, сериализовать в строку, передать в другую функцию, десерилизовать, разыменовать и изменить данное. Отследить данную цепочку в момент компиляции не возможно.
__>Это понадобилось, в частности, для того, чтобы на этапе компиляции отлавливать зацикливания.
Ловите зацикливания в рантайме: флажок и RAII — всё, что для этого нужно.
И каждый день — без права на ошибку...
Re[2]: "class const - метод" - можно ли организовать?
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, _hum_, Вы писали:
__>>Потому встал вопрос, есть ли в других языках или в новом стандарте с++ что-нибудь наподобие "class const — метода", который делает __>>константными ЛЮБЫЕ this данного класса в своей области видимости, и тем самым предотвращает всякое изменение любого (а значит и заданного)объекта данного класса?
BFE>Может и есть, но в С++ это не возможно, так как можно взять указатель на память, где лежит данное, привести его void*, сериализовать в строку, передать в другую функцию, десерилизовать, разыменовать и изменить данное. Отследить данную цепочку в момент компиляции не возможно.
ну, с таким подходом, так и const-спецификаторы невозможны, и в вобще ничего невозможно, кроме двочиных числе в ячеках памяти. речь же о том, чтобы дать возможность отслеживать НЕПРЕДНАМЕРЕННУЮ модификацию.
__>>Это понадобилось, в частности, для того, чтобы на этапе компиляции отлавливать зацикливания. BFE>Ловите зацикливания в рантайме: флажок и RAII — всё, что для этого нужно.
ага, особенно, если это система автопилота самолетом.
Re[3]: "class const - метод" - можно ли организовать?
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, _hum_, Вы писали:
__>>ага, особенно, если это система автопилота самолетом.
BFE>В системе автопилота самолета используется паттерн сигнал-слот? Не делайте так.
во-первых, вы так сказали, не я;
во-вторых, чем вас не устраивает сигнал-слот архитектура?
ну, а в-третьих, речь шла про общую проблему гарантии немодифицируемости методом полей объекта (решение которой, в частности, позволило бы решить проблему отслеживания циклов на этапе написания кода).
Re[5]: "class const - метод" - можно ли организовать?
Здравствуйте, _hum_, Вы писали:
__>во-вторых, чем вас не устраивает сигнал-слот архитектура?
Не устраивает двумя моментами:
1. Возможность возникновения event/signal loop. Невозможность доказать статически отсутсвие таких циклов.
2. Инфраструктура signal/slot занимает ресурсы. Как минимум память.
При условии наличия альтернативных механизмов свободных от 1 и 2 использование сигнал-слот представляется спорным.
Re[6]: "class const - метод" - можно ли организовать?
Здравствуйте, c-smile, Вы писали:
CS>Здравствуйте, _hum_, Вы писали:
__>>во-вторых, чем вас не устраивает сигнал-слот архитектура?
CS>Не устраивает двумя моментами:
CS>1. Возможность возникновения event/signal loop. Невозможность доказать статически отсутсвие таких циклов.
так а разве этим не грешат любые варианты организации двунаправленной связи между объектами?
CS>2. Инфраструктура signal/slot занимает ресурсы. Как минимум память.
CS>При условии наличия альтернативных механизмов свободных от 1 и 2 использование сигнал-слот представляется спорным.
а можно узнать об альтернативных механизмах, которые бы преодолевали эти два пункта, при том же самом функционале?
Re[5]: "class const - метод" - можно ли организовать?
Здравствуйте, _hum_, Вы писали:
__>>>ага, особенно, если это система автопилота самолетом. BFE>>В системе автопилота самолета используется паттерн сигнал-слот? Не делайте так. __>во-первых, вы так сказали, не я;
Я не предлагал использовать паттерн сигнал-слот для машин от которых может зависеть жизнь людей.
А что, собственно, я сказал? Я предложил добавить проверку времени исполнения. Проверка времени исполнения не может существенно замедлить код использующий сигнал-слот-архитектуру, так как на данный момент любая реализация этого патерна включает в себя приведение типа при каждом вызове слот-функции. А всякое такое приведение типа включает в себя проверку указателя на NULL во время приведения типа. Так что добавление проверки одного флажка не может существенно замедлить работу.
__>во-вторых, чем вас не устраивает сигнал-слот архитектура?
Тем, что сигнал-слот архитектура существенно повышает сложность понимания кода при одновременном снижении сложности разработки. Создаётся иллюзия, что эта архитектура упрощает код при фактическом его усложнении из-за динамического связывания вызовов функций. (Собственно то, что вам потребовалось такая хитрая проверка константности говорит именно об этом.) Упрощенно можно сказать, что эта архитектура приводит к тому, что вместо кода:
void f()
{
fun1();
fun2();
fun3();
fun4();
}
программист видит код:
void f()
{
...
}
где троеточие заменяется вызовами функций в самых произвольных местах проекта. Таким образом теряется локальность изменений и за изменениями становится сложно следить.
Если вы действительно хотите применить эту архитектуру для чего-то такого, от чего может зависеть жизнь людей, то вам жизненно необходимо вести документацию отображающую все деревья вызовов сигнал-слот.
__>ну, а в-третьих, речь шла про общую проблему гарантии немодифицируемости методом полей объекта (решение которой, в частности, позволило бы решить проблему отслеживания циклов на этапе написания кода).
Это ведь не единственная проблема данной архитектуры. Замените
void C_B::foo_B(void){ m_pA->set_x(10);}
на
void C_B::foo_B(void){ delete m_pA;}
и вы получите не менее эпический краш даже при гарантии немодифицируемости методом полей объекта.
И каждый день — без права на ошибку...
Re[13]: "class const - метод" - можно ли организовать?
0) заводит стек функций
__>не совсем понял, в каком месте (и в какой ситуации) не пройдет описанный мною выше алгоритм?
Да с самого начала. В общем случае у компилятора нет никакой возможности построить этот стек вызовов. Вот например: метод foo_B класса C_B вызывается через указатель на абстрактный базовый класс. О том, что вызвался метод именно в классе C_B мы можем узнать только в runtime.
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
Re[6]: "class const - метод" - можно ли организовать?
Здравствуйте, B0FEE664, Вы писали:
BFE>где троеточие заменяется вызовами функций в самых произвольных местах проекта. Таким образом теряется локальность изменений и за изменениями становится сложно следить.
а по моему это типичный пример динамического связывания. с таким подходом, под подозрение должны попасть и виртуальные функции
а по сути дела такой подход позволяет внедрить что-то типа микросервисной архитектуры внутри одного приложения, когда одни модули предоставляю.т сервисы, другие их используют, и обе стороны работают соврешенно независимо, не пытаясь контролировать кто их вызывал или кого они вызвали "на другой стороне" шины
Люди, я люблю вас! Будьте бдительны!!!
Re[6]: "class const - метод" - можно ли организовать?
BFE>А что, собственно, я сказал? Я предложил добавить проверку времени исполнения. Проверка времени исполнения не может существенно замедлить код использующий сигнал-слот-архитектуру, так как на данный момент любая реализация этого патерна включает в себя приведение типа при каждом вызове слот-функции. А всякое такое приведение типа включает в себя проверку указателя на NULL во время приведения типа. Так что добавление проверки одного флажка не может существенно замедлить работу.
самый главный недостаток динамического проверки, что ошибки ловятся уже в процессе работы, а значит, прерывают нормальное течение программы. к тому же не все их можно самому предугадать и словить. вот потому-то и стараются как можно больше проверок возложить на компилятор.
__>>во-вторых, чем вас не устраивает сигнал-слот архитектура? BFE>Тем, что сигнал-слот архитектура существенно повышает сложность понимания кода при одновременном снижении сложности разработки. Создаётся иллюзия, что эта архитектура упрощает код при фактическом его усложнении из-за динамического связывания вызовов функций. (Собственно то, что вам потребовалось такая хитрая проверка константности говорит именно об этом.) Упрощенно можно сказать, что эта архитектура приводит к тому, что вместо кода: BFE>
BFE>где троеточие заменяется вызовами функций в самых произвольных местах проекта. Таким образом теряется локальность изменений и за изменениями становится сложно следить. BFE>Если вы действительно хотите применить эту архитектуру для чего-то такого, от чего может зависеть жизнь людей, то вам жизненно необходимо вести документацию отображающую все деревья вызовов сигнал-слот.
погодите-погодите, кто говорил, что речь идет о динамическом связывании? во многих случаях (в том числе в моем) можно обойтись и статическим (никаких виртуальных функций, никаких observer-pattern, — обычная "магия шаблонов". см., например, библиотеку boost). но от этого проблема не пропадает (что указывает на ее общий характер).
__>>ну, а в-третьих, речь шла про общую проблему гарантии немодифицируемости методом полей объекта (решение которой, в частности, позволило бы решить проблему отслеживания циклов на этапе написания кода). BFE>Это ведь не единственная проблема данной архитектуры. Замените BFE>
BFE>и вы получите не менее эпический краш даже при гарантии немодифицируемости методом полей объекта.
принципиальное отличие — вы можете и сами это проконтролировать (например, проверкой выполнения условия возможности удаления или использовать RAII). а вот отследить скрытые циклические зависимости, как в моем примере — это уже во многом неподъемная для программиста задача. потому и должен в этом случае приходить на помощь компилятор.
Re[14]: "class const - метод" - можно ли организовать?
Здравствуйте, VTT, Вы писали:
VTT>Здравствуйте, _hum_, Вы писали:
VTT>0) заводит стек функций
__>>не совсем понял, в каком месте (и в какой ситуации) не пройдет описанный мною выше алгоритм?
VTT>Да с самого начала. В общем случае у компилятора нет никакой возможности построить этот стек вызовов. Вот например: метод foo_B класса C_B вызывается через указатель на абстрактный базовый класс. О том, что вызвался метод именно в классе C_B мы можем узнать только в runtime.
кхм.. извиняюсь, а как, по-вашему, компилятор генерирует код, если он не в курсе, кто что будет вызывать?
может, давайте все-таки на конкретном примере, и я вместо компилятора попробую вам пройтись по алгоритму?
если брать мой первоначальный пример, и считать, что там
void foo_A(void)class_const;
то компилятор будет действовать так:
— первый раз пробегает по тексту программы и заносит себе в память информацию о том, какие функции в программе объявлены (какие у них сигнатуры), и где у каждой объявленной функции определение (текст тела функции);
— второй раз пробегает по тексту программы и, встретив декларацию функции "foo_A(void)class_const", заносит ее первой в стек;
а дальше по п. 2)
берет с вершины стека foo_A(void)class_const и переходит к ее телу (информация о том, где оно , уже имеется после первого прохода):
void C_A::foo_A(void)const
{
m_pB->foo_B();
}
в теле видит вызов функции void foo_B(void), потому заносит его сигнатуру в стек; и т.д.
в каком месте он может споткнуться?
upd. может, вы имели в виду виртуальные функции? ну, так о них пока речь не шла (там можно либо все проверять, либо договориться, что в таких случаях виртуальные использовать нельзя).
Здравствуйте, _hum_, Вы писали:
__>кхм.. извиняюсь, а как, по-вашему, компилятор генерирует код, если он не в курсе, кто что будет вызывать?
Для компиляции ему вполне достаточно знать сигнатуру метода и откуда он берется (содержится в этом же исполняемом файле или импортируется).
__>- первый раз пробегает по тексту программы и заносит себе в память информацию о том, какие функции в программе объявлены (какие у них сигнатуры), и где у каждой объявленной функции определение (текст тела функции);
Дело в том, что как раз определения функции (текст тела функции) во время компиляции у компилятора может и не быть (я бы даже сказал что обычно нет).
Вот представьте, что класс C_B делает кто-то другой, а вам дает только заголовочный файл с объявлением этого класса и .dll (.lib) с его реализацией. А кода тела функции нет. И так со всеми системными и библиотечными функциями.
Даже если у вас есть код функции, он будет виден компилятору только если она inline или проект собирается с LTCG.
__>upd. может, вы имели в виду виртуальные функции? ну, так о них пока речь не шла (там можно либо все проверять, либо договориться, что в таких случаях виртуальные использовать нельзя).
Виртуальные я тоже имел ввиду. В начале вы вроде как упоминали сигнал-слоты, и на ум сразу приходит qt с его qobject и повальной виртуальностью.
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
Re[16]: "class const - метод" - можно ли организовать?
Здравствуйте, VTT, Вы писали:
VTT>Здравствуйте, _hum_, Вы писали:
__>>- первый раз пробегает по тексту программы и заносит себе в память информацию о том, какие функции в программе объявлены (какие у них сигнатуры), и где у каждой объявленной функции определение (текст тела функции);
VTT>Дело в том, что как раз определения функции (текст тела функции) во время компиляции у компилятора может и не быть (я бы даже сказал что обычно нет). VTT>Вот представьте, что класс C_B делает кто-то другой, а вам дает только заголовочный файл с объявлением этого класса и .dll (.lib) с его реализацией. А кода тела функции нет. И так со всеми системными и библиотечными функциями. VTT>Даже если у вас есть код функции, он будет виден компилятору только если она inline или проект собирается с LTCG.
ну, разве только если с dll, потому как при статической компоновке (.lib) теоретически есть возможность перед построение окончательного кода, просмотреть библиотеки (почему-то интуитивно кажется, что выполнить алгоритм можно и по ассемблерному коду, зная изначально, какие типы участвуют в сигнатуре. а если и нет, то на будущее можно просто добавлять в них необходимую информацию).
но вообще, контраргумент понял — данное понятие на текущий момент не допускает раздельной компиляции...
__>>upd. может, вы имели в виду виртуальные функции? ну, так о них пока речь не шла (там можно либо все проверять, либо договориться, что в таких случаях виртуальные использовать нельзя).
VTT>Виртуальные я тоже имел ввиду. В начале вы вроде как упоминали сигнал-слоты, и на ум сразу приходит qt с его qobject и повальной виртуальностью.
не, я не люблю виртуальность, потому пока стараюсь обходиться статическим вариантом реализации сигналов-слотов (boost::signals2, да и qt ввел уже возможность статической реализации).
Re: "class const - метод" - можно ли организовать?
Здравствуйте, _hum_, Вы писали:
__>Потому встал вопрос, есть ли в других языках или в новом стандарте с++ что-нибудь наподобие "class const — метода", который делает константными ЛЮБЫЕ this данного класса в своей области видимости, и тем самым предотвращает всякое изменение любого (а значит и заданного)объекта данного класса?
Навесить константность на все объекты теоретически можно тремя способами:
1) через покрытие кода (мечты, мечты...)
class X;
void foo(X* x) {
bar(x);
#pragma TURN CONST ON
buz(x); // вот здесь - и далее вглубь - константность навешана на всё#pragma TURN CONST OFF
bar(x);
buz(x);
}
компилятор должен провалиться в bar() и неявно навесить там константность
2) через типы — например, на шаблонах (фактически, это то же покрытие кода) — гору кода переписать, либо выбрать другой язык (скалу)
3) через, внезапно, рантаймовые проверки.
Просто натыкать флажков и ассертов
class X {
#ifdef _DEBUG
bool is_mutable = true;
thread_local static bool all_mutable = true;
void turn_mutable(bool m) { is_mutable = m; }
static void turn_all_mutable(bool m) { all_mutable = m; }
void assert_mutable() { assert(is_mutable && all_mutable); }
#else
void turn_mutable(bool m) {}
static void turn_all_mutable(bool m) {}
void assert_mutable() {}
#endif
.....
};
void foo(X* x) {
bar(x);
X::turn_all_mutable(false);
buz(x);
X::turn_all_mutable(true);
bar(x);
buz(x);
}
Я нарочно не вдаюсь в нюансы — понятно, что тут логика скорее не двоичная, а счётчика вложенных запретов. И под это дело можно и RAII присобачить, и делать это нужно будет грамотно...
Перекуём баги на фичи!
Re: "class const - метод" - можно ли организовать?
Здравствуйте, _hum_, Вы писали:
__>самый главный недостаток динамического проверки, что ошибки ловятся уже в процессе работы, а значит, прерывают нормальное течение программы. к тому же не все их можно самому предугадать и словить. вот потому-то и стараются как можно больше проверок возложить на компилятор.
Тут я согласен.
__>погодите-погодите, кто говорил, что речь идет о динамическом связывании? во многих случаях (в том числе в моем) можно обойтись и статическим (никаких виртуальных функций, никаких observer-pattern, — обычная "магия шаблонов". см., например, библиотеку boost). но от этого проблема не пропадает (что указывает на ее общий характер).
Да, речь идёт о динамическом связывании, так как вы говорите об архитектуре сигнал-слот. Архитектура сигнал-слот сводится к динамическому созданию списков функций — других реализаций я не видел. (Я, кстати, и не уверен, что в принципе можно создать статический вариант этой архитектуры без использования шаблонных виртуальных методов, которых в С++ нет)
__>принципиальное отличие — вы можете и сами это проконтролировать (например, проверкой выполнения условия возможности удаления или использовать RAII). а вот отследить скрытые циклические зависимости, как в моем примере — это уже во многом неподъемная для программиста задача. потому и должен в этом случае приходить на помощь компилятор.
Я не вижу разницы. Нет никакой проблемы в том, чтобы узнать во время выполнения, что данная функция вызвана из самой себя через последовательность других функций.
И каждый день — без права на ошибку...
Re[8]: "class const - метод" - можно ли организовать?
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, _hum_, Вы писали:
__>>погодите-погодите, кто говорил, что речь идет о динамическом связывании? во многих случаях (в том числе в моем) можно обойтись и статическим (никаких виртуальных функций, никаких observer-pattern, — обычная "магия шаблонов". см., например, библиотеку boost). но от этого проблема не пропадает (что указывает на ее общий характер).
BFE>Да, речь идёт о динамическом связывании, так как вы говорите об архитектуре сигнал-слот. Архитектура сигнал-слот сводится к динамическому созданию списков функций — других реализаций я не видел. (Я, кстати, и не уверен, что в принципе можно создать статический вариант этой архитектуры без использования шаблонных виртуальных методов, которых в С++ нет)
возможно, дело в терминологии. я говорю лишь, что есть реализации сигнал-слот без использования виртуальных функций (просто используются более продвинутые варианты call back-функций, позволяющие на этапе компиляции отслеживать корректность (по типам) вызовов и писать код безо всяких понижающих приведений типов).
__>>принципиальное отличие — вы можете и сами это проконтролировать (например, проверкой выполнения условия возможности удаления или использовать RAII). а вот отследить скрытые циклические зависимости, как в моем примере — это уже во многом неподъемная для программиста задача. потому и должен в этом случае приходить на помощь компилятор.
BFE>Я не вижу разницы. Нет никакой проблемы в том, чтобы узнать во время выполнения, что данная функция вызвана из самой себя через последовательность других функций.
вот здесь хотелось бы поподробнее (у вас 1000 разных функций, которые друг друга вызывают, и нужно определить, есть ли вариант зацикливания).
Re[8]: "class const - метод" - можно ли организовать?
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, _hum_, Вы писали:
__>>Потому встал вопрос, есть ли в других языках или в новом стандарте с++ что-нибудь наподобие "class const — метода", который делает константными ЛЮБЫЕ this данного класса в своей области видимости, и тем самым предотвращает всякое изменение любого (а значит и заданного)объекта данного класса?
К>Навесить константность на все объекты теоретически можно тремя способами:
К>1) через покрытие кода (мечты, мечты...) К>
К>class X;
К>void foo(X* x) {
К> bar(x);
К> #pragma TURN CONST ON
К> buz(x); // вот здесь - и далее вглубь - константность навешана на всё
К> #pragma TURN CONST OFF
К> bar(x);
К> buz(x);
К>}
К>
К>компилятор должен провалиться в bar() и неявно навесить там константность
К>2) через типы — например, на шаблонах (фактически, это то же покрытие кода) — гору кода переписать, либо выбрать другой язык (скалу) К>
К>Я нарочно не вдаюсь в нюансы — понятно, что тут логика скорее не двоичная, а счётчика вложенных запретов. И под это дело можно и RAII присобачить, и делать это нужно будет грамотно...
да, 1) — наверное как раз то, что нужно было бы — обобщение подхода с const-спецификаторами.
2) — немного запутанный и, как мне показалось, все-таки не решающий проблемы с косвенными изменениями (как в исходном моем примере)
3) — это не подходит (уже обсуждалось ранее).
Re[9]: "class const - метод" - можно ли организовать?
Здравствуйте, _hum_, Вы писали: BFE>> Нет никакой проблемы в том, чтобы узнать во время выполнения, что данная функция вызвана из самой себя через последовательность других функций. __>вот здесь хотелось бы поподробнее (у вас 1000 разных функций, которые друг друга вызывают, и нужно определить, есть ли вариант зацикливания).
Я имел ввиду это:
void g()
{
f();
}
void f()
{
static bool b = false; // если f() - член класса, то b может/должна быть нестатическим членом классаif ( b )
{
// cyclereturn;
}
b = true; // RAII must be here
g();
b = false;
}
по сравнению с накладными расходами signal-slot это мелочь.
не вариант
Ещё, как вариант, можно завести глобальный map<void*, bool> g_stack в который добавлять, проверять и удалять на выходе флажок g_stack[this] = true ... false... Но это слишком накладно будет.
И каждый день — без права на ошибку...
Re[3]: "class const - метод" - можно ли организовать?
Здравствуйте, _hum_, Вы писали:
К>>1) через покрытие кода (мечты, мечты...) К>>2) через типы — например, на шаблонах (фактически, это то же покрытие кода) — гору кода переписать, либо выбрать другой язык (скалу) К>>3) через, внезапно, рантаймовые проверки.
__>да, 1) — наверное как раз то, что нужно было бы — обобщение подхода с const-спецификаторами.
Проблема в том, что компилятору нужно давать подсказки, чего ИМЕННО ты хочешь ограничить.
Семейство объектов может состоять из
— текущего объекта (через все мыслимые указатели на него)
— его агрегатов и связей (поддерево, наддерево), в т.ч. других типов — вопрос, где проходит граница владения?
— всех объектов данного класса в текущем потоке
— всех объектов данного класса во всех потоках, с учётом пинг-понга и отложенных вызовов через границы потока и очереди сообщений
Тупо зарубить все объекты данного класса — это, похоже, ты от отчаяния захотел.
__>2) — немного запутанный и, как мне показалось, все-таки не решающий проблемы с косвенными изменениями (как в исходном моем примере)
Очень даже решающий.
Прямой доступ по ссылкам/указателям — константность поверхностная, слишком опасно и легко снимается.
Заменяем на доступ через геттеры, с глубокой константностью.
Там, где функция внешняя, придётся её насильно параметризовать, — например, как я это показал
Если не нравятся шаблоны, можно ограничиться рефакторингом вот такого рода
class X {
X* xz_;
public:
// стандартный приём: делаем глубокую константность
X* xz() { return xz_; }
X const* xz() const { return xz_; }
};
template<class T> class deep_constness_ptr {
T* p_;
public:
T* get();
T const* get() const;
// и так далее, весь обвес класса-указателя
};
class X1 {
public:
deep_constness_ptr<X1> xz_;
};
// и теперь самое страшное!
//namespace GodNamespace // былоstruct GodContext { // сталоstatic X* x();
static X const* x() const; // дублируем все функции примитивного доступа
// либо заменяем обычные указатели на глубокие
deep_constness_ptr<X> y;
// и переписываем сложные функции - добавляя нужную константностьvoid bar();
void buz() const;
// на самый крайний случай, оставим себе право на шаблонtemplate<class GodContextWithOrWithoutConst>
static void foo(GodContextWithOrWithoutConst* myself) {
myself->x();
myself->y;
myself->foo();
myself->bar(); // где-то мы и по пальцам получим!
}
};
__>3) — это не подходит (уже обсуждалось ранее).
Но лучше иметь под рукой и такие проверки тоже.
Не все тесты и покрытия кода легко отобразить в систему типов. Можно — но иногда слишком мутно и дорого. Поэтому ассерты рулят.
Перекуём баги на фичи!
Re[2]: "class const - метод" - можно ли организовать?
Здравствуйте, SaZ, Вы писали:
SaZ>Соседняя ветка про Qt с топик стартером позволит более просто сформулировать хотелку автора.
SaZ>_hum_ хочет переложить написание логики приложения с разработчика на компилятор. Кто знаком с Qt, может почитать изначальный вопрос: http://rsdn.ru/forum/cpp.qt/6188183.flat#6188183
Ой, либо вы увидели нечто большее в моем том вопросе, либо все-таки не совсем поняли этот. Речь там и тут шла о несколько разных (с моей точки зрения) вещах — там — вопрос чисто технического характера — о нюансах связывания неконстантного слота в теле константного метода этого же класса, а здесь о возможности использования const-спецификатора для полной гарантии неизменности полей объекта, что, в частности, давало бы механизм предотвращения зацикливания.
Re[9]: "class const - метод" - можно ли организовать?
Здравствуйте, Ops, Вы писали:
BFE>>без использования шаблонных виртуальных методов, которых в С++ нет Ops>deleter у shared_ptr — это оно?
Не совсем. Для deleter'a есть "иерархия" из двух классов: базовый класс имеет виртуальную функцию, которая в шаблонном наследнике перекрыта. А сам shared_ptr хранит указатель на базовый класс, а не на производный класс (объект которого создаётся по new) — в результате мы имеем обычное динамическое связывание.
И каждый день — без права на ошибку...
Re[10]: "class const - метод" - можно ли организовать?
Здравствуйте, B0FEE664, Вы писали: BFE>Здравствуйте, _hum_, Вы писали: BFE>>> Нет никакой проблемы в том, чтобы узнать во время выполнения, что данная функция вызвана из самой себя через последовательность других функций. __>>вот здесь хотелось бы поподробнее (у вас 1000 разных функций, которые друг друга вызывают, и нужно определить, есть ли вариант зацикливания). BFE>Я имел ввиду это: BFE>
BFE>void g()
BFE>{
BFE> f();
BFE>}
BFE>void f()
BFE>{
BFE> static bool b = false; // если f() - член класса, то b может/должна быть нестатическим членом класса
BFE> if ( b )
BFE> {
BFE> // cycle
BFE> return;
BFE> }
BFE> b = true; // RAII must be here
BFE> g();
BFE> b = false;
BFE>}
BFE>
BFE>по сравнению с накладными расходами signal-slot это мелочь. BFE>
не вариант
BFE>Ещё, как вариант, можно завести глобальный map<void*, bool> g_stack в который добавлять, проверять и удалять на выходе флажок g_stack[this] = true ... false... Но это слишком накладно будет.
так это проверка на этапе выполнения (мы же с вами уже это обсуждали). а речь о том, как обнаружить циклы и дать себе по рукам на этапе компиляции.
Re[4]: "class const - метод" - можно ли организовать?
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, _hum_, Вы писали:
К>>>1) через покрытие кода (мечты, мечты...) К>>>2) через типы — например, на шаблонах (фактически, это то же покрытие кода) — гору кода переписать, либо выбрать другой язык (скалу) К>>>3) через, внезапно, рантаймовые проверки.
__>>да, 1) — наверное как раз то, что нужно было бы — обобщение подхода с const-спецификаторами.
К>Проблема в том, что компилятору нужно давать подсказки, чего ИМЕННО ты хочешь ограничить. К>Семейство объектов может состоять из К>- текущего объекта (через все мыслимые указатели на него) К>- его агрегатов и связей (поддерево, наддерево), в т.ч. других типов — вопрос, где проходит граница владения? К>- всех объектов данного класса в текущем потоке К>- всех объектов данного класса во всех потоках, с учётом пинг-понга и отложенных вызовов через границы потока и очереди сообщений
ну, так потому ваш вариант 1) мне и импонирует — в него в качестве параметров можно прописывать, неизменность какого множества объектов данного класса должна быть выдержана при вызове метода.
К>Тупо зарубить все объекты данного класса — это, похоже, ты от отчаяния захотел.
да. и отчасти потому, что 1) вроде как просто реализовать (но потом при разговоре с VTT выяснилось, что есть проблема с проверкой бинарных модулей); 2) вроде как имеет содержательное (а не техническое) значение (эдакий аналог static).
__>>2) — немного запутанный и, как мне показалось, все-таки не решающий проблемы с косвенными изменениями (как в исходном моем примере)
К>Очень даже решающий.
К>Прямой доступ по ссылкам/указателям — константность поверхностная, слишком опасно и легко снимается. К>Заменяем на доступ через геттеры, с глубокой константностью.
К>Там, где функция внешняя, придётся её насильно параметризовать, — например, как я это показал
К>Если не нравятся шаблоны, можно ограничиться рефакторингом вот такого рода К>
К>class X {
К> X* xz_;
К>public:
К> // стандартный приём: делаем глубокую константность
К> X* xz() { return xz_; }
К> X const* xz() const { return xz_; }
К>};
К>template<class T> class deep_constness_ptr {
К> T* p_;
К>public:
К> T* get();
К> T const* get() const;
К> // и так далее, весь обвес класса-указателя
К>};
К>class X1 {
К>public:
К> deep_constness_ptr<X1> xz_;
К>};
К>// и теперь самое страшное!
К>//namespace GodNamespace // было
К>struct GodContext { // стало
К> static X* x();
К> static X const* x() const; // дублируем все функции примитивного доступа
К> // либо заменяем обычные указатели на глубокие
К> deep_constness_ptr<X> y;
К> // и переписываем сложные функции - добавляя нужную константность
К> void bar();
К> void buz() const;
К> // на самый крайний случай, оставим себе право на шаблон
К> template<class GodContextWithOrWithoutConst>
К> static void foo(GodContextWithOrWithoutConst* myself) {
К> myself->x();
К> myself->y;
К> myself->foo();
К> myself->bar(); // где-то мы и по пальцам получим!
К> }
К>};
К>
ну, это еще страшнее смотрится. давайте лучше так — представим, что вы пишете class С_A из моего первого примера, а я пишу class С_B. и вот какой общий подход вы предлагаете, чтобы не дать возможности произойти модификации при вызове foo_A?
Re[5]: "class const - метод" - можно ли организовать?
Здравствуйте, _hum_, Вы писали:
__>ну, так потому ваш вариант 1) мне и импонирует — в него в качестве параметров можно прописывать, неизменность какого множества объектов данного класса должна быть выдержана при вызове метода.
Ага, "компилятор, сделай меня счастливым".
__>ну, это еще страшнее смотрится. давайте лучше так — представим, что вы пишете class С_A из моего первого примера, а я пишу class С_B. и вот какой общий подход вы предлагаете, чтобы не дать возможности произойти модификации при вызове foo_A?
Придётся любым способом вводить сущность "сессия".
Из CA::foo_A()const создаётся сессия, запрещающая изменять этот (или все) экземпляр CA.
В CA::set_x() допускается исключительно сессия, разрешающая изменять его.
А вот что бы мы делали, если бы у нас в языке не было const? Или если бы мы хотели более детально ограничивать права — например, x можно менять, а y нельзя?
Поэтому забудем про "компилятор, сделай меня счастливым".
Итак, способ номер раз. Хранить сессию прямо в объекте, — членами-флажками. Передача сессии осуществляется неявно, "состоянием реального мира".
С одной стороны, удобно — минимум писанины. С другой — есть риск чего-нибудь забыть. И с реентером проблемы.
Способ номер два: создавать объект сессии и передавать его: жетоны, куки, что угодно.
// не конкретизирую недраstruct Session;
bool allowed_to_set_x(Session);
bool allowed_to_set_y(Session);
class CA {
void set_x(Session s, int x) { assert(allowed_to_set_x(s); m_x = x; }
void foo() { m_pB->foo(make_session_with_all_permissions()); }
void bar() const { m_pB->bar(make_session_without_permissions()); }
// вид сессии можно и из константности выводить - ИНОГДА
Session make_typical_session() { return make_session_with_all_permissions(); }
Session make_typical_session() const { return make_session_without_permissions(); }
...
};
class CB {
void foo(Session s) { m_pA->set_x(s, 123); }
void bar(Session s) { m_pA->set_x(s, 456); } // если оно вызывается ТОЛЬКО из CA::bar()const, получим по пальцам
};
Это, безусловно, затратно, — но если мы один раз договорились, что внедряем сессии, — то дальше всё будет просто.
Самое главное, что мы сжигаем мосты и больше не позволяем бесконтрольно вызывать изменяющие методы. Если кто-то хочет сделать CA::set_x(), он должен получить откуда-то права. А точки выдачи прав — по пальцам сосчитать.
Это как private-секции, только рантайм.
Способ номер три. Просто заменить полиморфизм времени исполнения (проверку флажков в объекте сессии) на полиморфизм времени компиляции (семейство типов сессий и шаблоны).
Причём мы можем сколь угодно сложную информацию упаковывать — в mpl_vector, например.
Разница между "два" и "три" минимальная, — рефакторинг там в обе стороны почти бесплатно делается.
Наконец, ещё три способа.
Пусть CB хранит const CA*, то есть, исходно будет ограничен в правах. И только там, где CA ему явно разрешает поковыряться в себе, — пусть CA передаёт неконстантный указатель на себя.
Аналогично, если нужно интерфейс к CA урезАть по другим критериям, — например, разрешать доступ к тем или иным переменным, — тогда старые добрые интерфейсы решают. (По сути, С++ делает нам бесплатно два интерфейса у объекта — с константными и неконстантными членами. Но мы, вдохновившись, и руками можем написать интерфейсы, верно?)
Развитие этой темы — паттерн посетитель, — когда CA передаёт не прямо вот себя, а объект с функциями, которые можно дёргать.
И второе развитие — фабрика. Когда CB хранит не прямо CA, а некоторую заготовку, из которой можно получить временный указатель константного или неконстантного CA*. Ровно в соответствии с теми правами, которые указаны в жетоне сессии.
Это избавит от необходимости инструментировать все методы CA.
В качестве заготовки может послужить и сам const CA* — вот так:
class CA {
class Permitted {
private: Permitted() {} // чтоб неповадно былоfriend class CA;
};
void set_x(int x);
void set_y(int y);
CA* get_mutable_myself(Permitted) const { return const_cast<CA*>(this); }
void foo() { m_pA->foo(Permitted()); } // захотели и разрешилиvoid bar() const { m_pB->bar(); }
};
class CB {
CA const* m_pA;
void foo(CA::Permitted p) {
CA* pA = m_pA->get_mutable_myself(p); // реализовали право
pA->set_x(123);
pA->set_y(456);
};
void bar() {
// никак не взять неконстантный pA.
}
};
Перекуём баги на фичи!
Re[7]: "class const - метод" - можно ли организовать?
Здравствуйте, _hum_, Вы писали:
CS>>Не устраивает двумя моментами:
CS>>1. Возможность возникновения event/signal loop. Невозможность доказать статически отсутсвие таких циклов.
__>так а разве этим не грешат любые варианты организации двунаправленной связи между объектами?
А зачем там двунаправленный механизм?
CS>>2. Инфраструктура signal/slot занимает ресурсы. Как минимум память.
CS>>При условии наличия альтернативных механизмов свободных от 1 и 2 использование сигнал-слот представляется спорным.
__> а можно узнать об альтернативных механизмах, которые бы преодолевали эти два пункта, при том же самом функционале?
всякого рода event buses. Или как вариация оных bubbling events в browsers.
Используют тот факт что объекты уже находятся в дереве. Т.е. связь child parent уже есть.
Если child генерирует событие то это событие bubble up (здесь — всплывает) по цепочке от child ко всем его parent.
Таким образом если какому-то контейнеру нужно обработать событие от child он (контейнер) может это сделать без всякой подписки и механизма с ним ассоциированного. Плюс каждый parent может что-то добавить к событию или "потребить" (consume) его.
В Web events есть еще так называемая sinking или capturing фаза — http://catcode.com/domcontent/events/capture.html
Когда parents в иерархии получают события до детей. Т.е. в этой фазе parent может не пустить какое-то событие в child.
Т.е. механизм не требует вообще никакой доп. памяти и структур (кроме тех что уде наверняка есть типа parent child)
гибкий и мощный — позволяет разные event handling & delivering policy использовать.
принципиально свободен от проблемы циклов (loops).
Здравствуйте, c-smile, Вы писали:
CS>Здравствуйте, _hum_, Вы писали:
CS>>>Не устраивает двумя моментами:
CS>>>1. Возможность возникновения event/signal loop. Невозможность доказать статически отсутсвие таких циклов.
__>>так а разве этим не грешат любые варианты организации двунаправленной связи между объектами?
CS>А зачем там двунаправленный механизм?
ну, типичная задача — организация graphical user-interface для редактирования каких-то данных (модели данных) в варианте архитектуры "модель — представление".
для возможности редактирования нужна связь представление-> модель, для возможности оперативного отображения измененных данных (например, в нескольких представлениях), нужна связь модель->представление (для оповещение представлений, что данные модели изменились).
CS>>>2. Инфраструктура signal/slot занимает ресурсы. Как минимум память.
CS>>>При условии наличия альтернативных механизмов свободных от 1 и 2 использование сигнал-слот представляется спорным.
__>> а можно узнать об альтернативных механизмах, которые бы преодолевали эти два пункта, при том же самом функционале?
CS>всякого рода event buses. Или как вариация оных bubbling events в browsers.
CS>Используют тот факт что объекты уже находятся в дереве. Т.е. связь child parent уже есть.
CS>Если child генерирует событие то это событие bubble up (здесь — всплывает) по цепочке от child ко всем его parent. CS>Таким образом если какому-то контейнеру нужно обработать событие от child он (контейнер) может это сделать без всякой подписки и механизма с ним ассоциированного. Плюс каждый parent может что-то добавить к событию или "потребить" (consume) его.
CS>В Web events есть еще так называемая sinking или capturing фаза — http://catcode.com/domcontent/events/capture.html CS>Когда parents в иерархии получают события до детей. Т.е. в этой фазе parent может не пустить какое-то событие в child.
CS>Т.е. механизм CS> CS> не требует вообще никакой доп. памяти и структур (кроме тех что уде наверняка есть типа parent child) CS> гибкий и мощный — позволяет разные event handling & delivering policy использовать. CS> принципиально свободен от проблемы циклов (loops). CS>
и как этот механизм можно применить для решения указанной выше задачи организации архитектуры "модель-представление" в варианте, когда представлений несколько, и модель допускает редактирование через любое из них?
Re[6]: "class const - метод" - можно ли организовать?
Здравствуйте, Кодт.
Все это прекрасно, только вы всюду неявно налагаете на меня, пишущего класс C_B (помните, мы договорились — вы пишете класс С_A, а я C_B), ограничения на пользование классом C_A (то только обращение через сессии, то только через методы, возвращающие указатели на объекты C_A, то еще чего-то). А все договоренности ничего не стоят, ибо легко нарушаются (по забывчивости, невнимательности и т.п.). Я думал, вы предложите вариант, в котором я как создатель С_B ВЫНУЖДЕН буду делать так, как вам надо, ибо в противном случае буду получать некомпилирующийся код.
Re[7]: "class const - метод" - можно ли организовать?
Здравствуйте, _hum_, Вы писали:
__>Все это прекрасно, только вы всюду неявно налагаете на меня, пишущего класс C_B (помните, мы договорились — вы пишете класс С_A, а я C_B), ограничения на пользование классом C_A (то только обращение через сессии, то только через методы, возвращающие указатели на объекты C_A, то еще чего-то). А все договоренности ничего не стоят, ибо легко нарушаются (по забывчивости, невнимательности и т.п.). Я думал, вы предложите вариант, в котором я как создатель С_B ВЫНУЖДЕН буду делать так, как вам надо, ибо в противном случае буду получать некомпилирующийся код.
Если я написал класс, у которого в интерфейсе все ключевые функции требуют сессию, а конструктор сессии приватный, — то все клиенты будут вынуждены делать так, как мне надо. Иначе код будет некомпилирующийся, по причине несоответствующих сигнатур функций.
Писанины, конечно, при этом прибавится — синтаксический шум в виде этих самых идентификаторов сессий, либо в виде обёрток (классов и замыканий) над кодом-внутри-сессии.
Перекуём баги на фичи!
Re[8]: "class const - метод" - можно ли организовать?
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, _hum_, Вы писали:
__>>Все это прекрасно, только вы всюду неявно налагаете на меня, пишущего класс C_B (помните, мы договорились — вы пишете класс С_A, а я C_B), ограничения на пользование классом C_A (то только обращение через сессии, то только через методы, возвращающие указатели на объекты C_A, то еще чего-то). А все договоренности ничего не стоят, ибо легко нарушаются (по забывчивости, невнимательности и т.п.). Я думал, вы предложите вариант, в котором я как создатель С_B ВЫНУЖДЕН буду делать так, как вам надо, ибо в противном случае буду получать некомпилирующийся код.
К>Если я написал класс, у которого в интерфейсе все ключевые функции требуют сессию, а конструктор сессии приватный, — то все клиенты будут вынуждены делать так, как мне надо. Иначе код будет некомпилирующийся, по причине несоответствующих сигнатур функций. К>Писанины, конечно, при этом прибавится — синтаксический шум в виде этих самых идентификаторов сессий, либо в виде обёрток (классов и замыканий) над кодом-внутри-сессии.
ну, вот я беру ваш код К>Способ номер два: создавать объект сессии и передавать его: жетоны, куки, что угодно. К>
К>// не конкретизирую недра
К>struct Session;
К>bool allowed_to_set_x(Session);
К>bool allowed_to_set_y(Session);
К>class CA {
К> void set_x(Session s, int x) { assert(allowed_to_set_x(s); m_x = x; }
К> void foo() { m_pB->foo(make_session_with_all_permissions()); }
К> void bar() const { m_pB->bar(make_session_without_permissions()); }
К> // вид сессии можно и из константности выводить - ИНОГДА
К> Session make_typical_session() { return make_session_with_all_permissions(); }
К> Session make_typical_session() const { return make_session_without_permissions(); }
К> ...
К>};
К>class CB {
К> void foo(Session s) { m_pA->set_x(s, 123); }
К> void bar(Session s) { m_pA->set_x(s, 456); } // если оно вызывается ТОЛЬКО из CA::bar()const, получим по пальцам
К>};
К>
и просто видоизменяю наподобие:
class CB {
C_A* m_pA;
...
void foo(Session s) { m_pA->set_x(s, 123); }
//void bar(Session s) { m_pA->set_x(s, 456); } // если оно вызывается ТОЛЬКО из CA::bar()const, получим по пальцамvoid bar(Session s)
{
const Session SessionWithAllPermissions = m_pA->make_typical_session(); // поскольку m_pA есть неконтсантный указатель C_A*, то все ок - я получу сессию со всеми разрешениями на модификацию
m_pA->set_x(SessionWithAllPermissions, 456); // опять же, поскольку сессия все разрешает, то могу модифицировать
}
};
ну и? вызов CA::bar()const опять станет модифицирующим, и компилятор это проглотит.
Re[9]: "class const - метод" - можно ли организовать?
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, _hum_, Вы писали:
__>>ну, вот я беру ваш код __>>и просто видоизменяю наподобие:
К>А фигушки, если CA::make_typical_session приватный. Он не предназначен для того, чтобы посторонние его вызывали.
извиняюсь, а как тогда вообще можно установить новое значение C_A::m_x за пределами области видимости C_A и его наследников?
К>Этак и со старой доброй константностью можно было проворачивать читерство: К>
К>const CA* pA;
К>....
К>const_cast<CA*>(pA)->set_x(123); // эй компилятор, почему ты не дал мне здесь по пальцам?!
К>
нет, тут совсем другая ситуация — здесь в явном виде говорится — я сознательно нарушаю требования константности, и отвечаю за последствия.
а всюду выше речь велась о защите от нечаянной непредумышленной модификации.
Re[11]: "class const - метод" - можно ли организовать?
Здравствуйте, _hum_, Вы писали:
__>извиняюсь, а как тогда вообще можно установить новое значение C_A::m_x за пределами области видимости C_A и его наследников?
Ну как: войти в сессию, получить у компетентной стороны токен, с этим токеном пойти и вызвать метод-модификатор.
Сам же хотел обеспечить разграничение прав? Вот оно так и делается.
Дальше нюансы — насколько система типов позволяет уменьшить синтаксический шум.
И насколько компилятор способен покрытие кода отследить (либо мы сваливаем всё в рантайм, не доверяя и не нагружая компилятор, либо тащим всё в строгие типы и получаем дополнительный синтаксический шум и дополнительное время компиляции).
Перекуём баги на фичи!
Re[12]: "class const - метод" - можно ли организовать?
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, _hum_, Вы писали:
__>>извиняюсь, а как тогда вообще можно установить новое значение C_A::m_x за пределами области видимости C_A и его наследников?
К>Ну как: войти в сессию, получить у компетентной стороны токен, с этим токеном пойти и вызвать метод-модификатор.
так что мешает получить у "компетентной стороны" сессию, а потом использовать ее не по назначению?
К>Сам же хотел обеспечить разграничение прав? Вот оно так и делается.
да, но ваше решение все равно не спасает от "выстрела в ногу"
Re[13]: "class const - метод" - можно ли организовать?
Здравствуйте, _hum_, Вы писали:
__>так что мешает получить у "компетентной стороны" сессию, а потом использовать ее не по назначению?
Например, отсутствие публичного конструктора копирования. Передавать сессию только по ссылке.
А для любителей стрельбы по ногам — добавить в экземпляр сессии рантаймовую информацию, и обложить её ассертами.
__>да, но ваше решение все равно не спасает от "выстрела в ногу"
Если очень сильно захотеть, то в ногу можно на любом языке программирования выстрелить. Даже на супер-пупер-типизированном, вроде хаскелла и идриса. И на супер-пупер-обложенном проверками, вроде эйфеля и ады.
(Например, вставить код вида
и всё, автоматизированное покрытие кода — коту под хвост. список проблем — мне больше всего гипотеза Коллатца нравится).
Фантомный тип "с константностью" защищает от большого класса простых ошибок.
Фантом "nullable" и "non-null" — ещё от одного класса. (Это уже C#).
Другие фантомы требуют писанины. Даже на хаскелле и аде.
Суть приёма в том, что у нас есть слой пассивных типов, над которыми можно делать всё, что угодно (включая set_x), есть слой-изолятор, запрещающий работать с ними напрямую, и слой фантомных типов — обёрток над пассивными, в котором реализована логика разрешений.
Минимальные изменения в исходный код — это добавить токены сессий (фантом над void, фактически).
Мы как бы домысливаем, что все функции foo(T,U,V), set_x(int) и т.п. — имеют ещё один пустой аргумент — foo(T,U,V,void), set_x(int,void) — а затем заменяем void на фантом.
Крупные изменения — сделать фантомы над самими объектами. Собственно, как я уже сказал, квалификатор const — это оно. Но в данном случае его оказалось недостаточно, слишком уж тупая логика у компилятора.
Отчасти это административная мера. То есть, надо себя заставить хотя бы начать так писать.
И конечно, это не спасёт нас от читерства, если мы пойдём на такое читерство намеренно. Но, поскольку случайное читерство невозможно, это всегда ещё большая писанина, — то это спасает нас от ошибок.