Во многих учебниках встречался с утверждениями типа "константный обьект класса изменить невозможно", но в Visual С++ следующий код срабатывает без проблем:
class A
{
public:
A() : x(0) {}
virtual ~A() {}
int x;
void ff() { x += 5;}
};
void f(const A& a)
{
A * pA = const_cast<A *>(&a);
A& ra = *pA; // this transformation is no needed,
// just for the code appearance
ra.x = 5;
ra.ff();
}
int main()
{
A a;
cout << a.x << endl;
f(a);
cout << a.x << endl;
const A cA;
cout << cA.x << endl;
f(cA);
cout << cA.x << endl;
}
Где ошибка: в учебниках? Или Visual C++ 6.0 не соответствует стандарту? Кто — нибудь это знает?
А>void f(const A& a)
А>{
А> A * pA = const_cast<A *>(&a);
А> A& ra = *pA; // this transformation is no needed,
А> // just for the code appearance
А> ra.x = 5;
А> ra.ff();
А>}
const_cast нужен как раз для "снятия" константности, с объекта который константным не является изначально. все ок.
А>Где ошибка: в учебниках? Или Visual C++ 6.0 не соответствует стандарту? Кто — нибудь это знает?
Visual C++ 6.0 точно стандарту не соответствует но не в этой части
Здравствуйте, Аноним, Вы писали: А>Где ошибка: в учебниках? Или Visual C++ 6.0 не соответствует стандарту? Кто — нибудь это знает?
1. Утверждение стандарта, конечно, правильное... Но естественно, существуют способы обойти это ограничение... Доступ черех указатель- классический хак... Но есть и стандартный способ... В частности обратите внимание на слово mutable, позоляющее стандартным способом изменять константный объект... На РСДН уже неоднократно обсуждался вопрос о концептуальной и физической константности — поищите в поиске
2. VC6 очень плохо соответствует стандарту — старый очень, создавался ДО принятия официального стандарта...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, dip_2000, Вы писали:
_>const_cast нужен как раз для "снятия" константности, с объекта который константным не является изначально. все ок.
Спасибо, но это мне известно (можно догадаться по коду). Значит проблема в учебниках.
Может быть имеет смысл их исправить?
Здравствуйте, LaptevVV, Вы писали:
LVV>1. Утверждение стандарта, конечно, правильное... Но естественно, существуют способы обойти это ограничение... Доступ черех указатель- классический хак... Но есть и стандартный способ... В частности обратите внимание на слово mutable, позоляющее стандартным способом изменять константный объект... На РСДН уже неоднократно обсуждался вопрос о концептуальной и физической константности — поищите в поиске LVV>2. VC6 очень плохо соответствует стандарту — старый очень, создавался ДО принятия официального стандарта...
Спасибо за наводку. Но есть некое несоответствие при приведении типов через указатель — в этом случае оно срабатывает только с производными типами (классами). С натуральными C++ типами это не работает. В чем тут подвох? Может быть стоит стандарт почитать?
Здравствуйте, Аноним, Вы писали:
А>Где ошибка: в учебниках? Или Visual C++ 6.0 не соответствует стандарту? Кто — нибудь это знает?
5.2.11/7
[Note: Depending on the type of the object, a write operation through the pointer, lvalue or pointer to data
member resulting from a const_cast that casts away a const-qualifier68) may produce undefined behavior
(7.1.5.1). ]
7.1.5.1/4
Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const
object during its lifetime (3.8) results in undefined behavior.
7.1.5.1/5
[Example:
const int ci = 3; // cv-qualified (initialized as required)
ci = 4; // ill-formed: attempt to modify constint i = 2; // not cv-qualifiedconst int* cip; // pointer to const int
cip = &i; // OK: cv-qualified access path to unqualified
*cip = 4; // ill-formed: attempt to modify through ptr to constint* ip;
ip = const_cast<int*>(cip); // cast needed to convert const int* to int*
*ip = 4; // defined: *ip points to i, a non-const objectconst int* ciq = new const int (3); // initialized as requiredint* iq = const_cast<int*>(ciq); // cast required
*iq = 4; // undefined: modifies a const object
7.1.5.1/6
class X {
public:
mutable int i;
int j;
};
class Y {
public:
X x;
Y();
};
const Y y;
y.x.i++; //well-formed: mutable member can be modified
y.x.j++; //ill-formed: const-qualified member modified
Y* p = const_cast<Y*>(&y); // cast away const-ness of y
p->x.i = 99; // well-formed: mutable member can be modified
p->x.j = 99; // undefined: modifies a const member
—end example]
А вот этот кусок содержит неопределенное поведение, т.к. в данном случае имеет место модификация "настоящей" константы.
То, что программа выдает "правильные" результаты (до поры — до времени) — частный случай неопределенного поведения
Здравствуйте, dandy, Вы писали:
D>Но есть некое несоответствие при приведении типов через указатель — в этом случае оно срабатывает только с производными типами (классами). С натуральными C++ типами это не работает.
Приведи пример, где не работает.
D>Может быть стоит стандарт почитать?
Это из серии "Как выстрелить себе в ногу"
— с помощью const_cast — стрельба в ногу под общим наркозом
— с помощью C-style cast (который неявно трактуется как const_cast) — стрельба в ногу под раушем (кувалдой по черепу)
— с помощью non-const rvalue — стрельба в ногу под новокаиновой блокадой
— с помощью mutable члена — это стрельба в протез
struct Foo
{
mutable int x; // этот член можно менять всегдаint y; // этот член можно менять только у неконстантных объектов
Foo() {}
Foo& myself() { return *this; }
};
void bar(Foo& f)
{
f.y = 1;
}
int main()
{
const Foo f;
f.x = 1;
f.y = 1; // errorconst_cast<Foo&>(f).y = 1;
((Foo&)f).y = 1;
const_cast<Foo*>(&f)->y = 1;
((Foo*)&f)->y = 1;
Foo().y = 1; // error
bar(Foo()); // error
Foo().myself().y = 1;
bar(Foo().myself());
}
mutable и трюк с non-const rvalue — законны. В первом случае мы имеем дело с заведомо неконстантной переменной, а во втором — с временным объектом.
А вот const_cast — на страх и риск. Туда можно подсунуть настоящую константу, стрельба в которую приведёт к разнообразным неопределённым поведениям (от простенького вылета по Access Violation до необычной арифметики и логики).
Здравствуйте, Аноним, Вы писали:
А>Во многих учебниках встречался с утверждениями типа "константный обьект класса изменить невозможно", но в Visual С++ следующий код срабатывает без проблем:
А>Где ошибка: в учебниках? Или Visual C++ 6.0 не соответствует стандарту? Кто — нибудь это знает?
Скорее это учебники для начинающих. "константный обьект класса изменить невозможно", но если очень хочется, то можно.
Слово const больше нужно для программистов. Чтобы код читался легче, был более документированным.
const_cast нужен главным образом, для вызова из константных методов старых неконстантных функций, которые не меняют свой аргумент.
Изменить конечно можно, но это подобно этому:
Здравствуйте, Аноним, Вы писали:
А>Во многих учебниках встречался с утверждениями типа "константный обьект класса изменить невозможно",..
Я не знаток и не учебник, но попробую поделиться некоторыми соображениями по вопросу.
Общий смысл понятия константность сводится к тому, что мы заявляем, что некое "значение" объекта не может измениться.
Тут возникает вопрос, что такое "значение". Это собственно то, что объект представляет. Если объект представляет число -- значит "значение" -- это число. Например объект "рациональное число" в состояниях {1 / 2 } и в состоянии { 2 / 4 } представляет одно и то же "значение" 0.5.
Объект "строка" представляет значение "последовательность символов", при этом где именно хранится эта последовательность символов и как в "значение" строки не входит.
Я не хочу углубляться в понятие "значение" объекта, так как это уведёт очень далеко от понятия константонсти.
Главное понимать, что в состоянии объекта есть как бы две грани. Одна -- это подробности реализации, которые пользователю объекта не интересны и некое "значение", ради которого объект собственно и используется.
Во всяком случае так обстоят дела в хороших программах.
Так вот, часто так бывает, что при программировании полезно указать, что "значение" какого-то объекта не может измениться в течении работы какого-то алгоритма.
Тогда этот объект изнутри этого алгоритма представляют как константный. При этом у константного объекта можно вызывать только константные методы.
Это просто некий сервис, предоставляемый языком.
Если ты обозначил какой-то метод константным, то ты сообщаешь пользователю класса, что этот метод не меняет "значение" класса.
В принципе, за одним небольшим исключением, ты волен реализовывать константность метода (то есть неизменность "значения" объекта) как тебе наравится. Но для удобства, C++ предлагет некоторую модель константности, которая обычно интуитивно понятна почти во всём.
Идея модели очень простая. Все поля и базы константного объекта -- константны.
Тут есть две тонкости.
Тонкость 1 состоит в том, что если такое константное поле является указателем, то менять нельзя сам указатель (например нельзя начать ссылаться на другой объект), а вот сам указуемый объект менять как раз можно. При этом довольно часто в "значение" объемлещего класса входит не "значение" указателя, а "значение" указуемого объекта.
Так что заботу о сохранении семантики константности С++ в случае поля-указаетля перекладывает на программиста.
Тонкость 2 состоит в том, что в объекте может быть поле "значение" которого не входит в "значение" объекта.
То есть вполне законно можно менять "значение" такого поля даже из константного (то есть не меняющего "значение" объекта) метода. Для этого в C++ есть ключевое слово mutable, которым маркируются такие поля.
При учёте этих двух тонкостей ситуация становится уже почти радужной, но только до той поры, пока ты пользуешься совсем простыми моделями константности методов. Если же тебе зачем-то потребуется что-то "непростое", то уже станет тесно.
Например, если константность метода будет зависеть от "значения" его параметров, то С++ модель константности становится совсем уж обременительной.
Именно для таких случаев и используется const_cast, по идее.
То есть может быть написано что-то типа (MFC-style):
Ну а теперь настало время вернуться к тому самом "одному небольшому исключению".
Исключние наступает в случае, когда на сцену выступают константные объекты стандартных типов (числа, указатели и т. п.). Так как неизменность их "значений" реализует и гарантирует компилятор, то он может использовать информацию о их константности для оптимизации и реализации этих объектов и работы с ними. Поэтому, если ты что-то говоришь о константности каких-то объектов, то в конце концов ты обычно что-то говоришь о константоности находящихся в них в виде явных или неявных полей объектов стандартных типов. И если ты тут будешь слишком вольно обращаться с константностью, то ты можешь нарваться.
Это всего лишь вопрос в том как ограничить произвол программиста и компилятора в области обращения с константностью стандартных типов. В С++ принято следующее компромиссное соглашение на эту тему.
Константность бывает как бы " истинная" и "предписанная".
Истинной константой является переменная, которую создали как константу. Если это переменная стандартного типа, то компилятор может с ней вытворять много странного, скажем может её вообще не заводить, пока ты не используешь её адрес, а использовать всюду непосредственно её "значение", а может завести в read-only сегменте памяти и отмапировать её значение прямо из секции данных исполняемого файла, а может и ещё чего учудить.
Если это поле какой-то структуры с "истинной" константностью, то компилятор всё равно может хаккерить, хотя и меньше. Во всякмо случае не использовать значение реально расположенное в памяти, и расположить в read-only памяти может запросто. Короче говоря в отношении работы с "истинными констанатми" у компилятора есть дополнительные степени свободы. Правда эта свобода не распространяется на mutable поля даже "истинных констант".
В отличии от "истинной" константности, есть ещё и константность "предписанная". Она возникает, когда мы начинаем обращаться с неконстантным объектом, как с константным. Например имея к нему доступ через константную ссылку. Тогда руки у компилятора связаны и мы можем как угодно мухлевать с const_cast, не рискуя нарваться на последствия "излишней изобретательности" компилятора.
Так что снимать "истинную" константность опасно, потому что есть опасность нарваться на глюки, а "предписанную" опасно потому, что можно нарушить семантику константности объекта (то есть нарушить обещание не менять "значение" объекта).
Но первичным, тем не менее, тут является именно обещание не изменять "значение" объекта, а не какие-то там правила. Другое дело, что правила С++ обычно сразу подходят в большинсте случаев и нет нужды изобретать какую-то хитрую схему управления константностью объекта.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, <Аноним>, Вы писали:
К>Это из серии "Как выстрелить себе в ногу"
Это зачем?
К>- с помощью const_cast — стрельба в ногу под общим наркозом
Это по определению const_cast
К>- с помощью C-style cast (который неявно трактуется как const_cast) — стрельба в ногу под раушем (кувалдой по черепу)
Да? Вы и работающий пример можете привести? Через C-style cast?
К>- с помощью non-const rvalue — стрельба в ногу под новокаиновой блокадой
Вот этого не знал... Спасибо.
К>- с помощью mutable члена — это стрельба в протез
К>
К>struct Foo
К>{
К> mutable int x; // этот член можно менять всегда
К> int y; // этот член можно менять только у неконстантных объектов
К> Foo() {}
К> Foo& myself() { return *this; }
К>};
К>void bar(Foo& f)
К>{
К> f.y = 1;
К>}
К>int main()
К>{
К> const Foo f;
К> f.x = 1;
К> f.y = 1; // error
К> const_cast<Foo&>(f).y = 1;
К> ((Foo&)f).y = 1;
К> const_cast<Foo*>(&f)->y = 1;
К> ((Foo*)&f)->y = 1;
К> Foo().y = 1; // error
К> bar(Foo()); // error
К> Foo().myself().y = 1;
К> bar(Foo().myself());
К>}
К>
К>mutable и трюк с non-const rvalue — законны. В первом случае мы имеем дело с заведомо неконстантной переменной, а во втором — с временным объектом.
Надо смотреть.
К>А вот const_cast — на страх и риск. Туда можно подсунуть настоящую константу, стрельба в которую приведёт к разнообразным неопределённым поведениям (от простенького вылета по Access Violation до необычной арифметики и логики).
Вы уверены? Можно ссылку на стандарт?
Re: Вопрос знатокам стандарта C++
От:
Аноним
Дата:
04.09.07 14:42
Оценка:
А>Во многих учебниках встречался с утверждениями типа "константный обьект класса изменить невозможно", но в Visual С++ следующий код срабатывает без проблем:
Знаете, то что код работает у вас здесь и сейчас совершенно не означает то что он будет работать у всех, везде и всегда. Константность объекта это хинт компилятору на то что класс никто не будет изменять. Зная это компилятор может например положить глобальный экземпляр класса в read-only memory (а на некоторых платформах даже в ROM записать). Соответственно снятие констанстности и модификация объекта у которогоthis указывает на read-only память потом может обернуться в access violation. А еще константность объекта может указатть компилятору на то что поля-члены никогда не будут изменяться после вызова конструктора. Соответственно он возьмет и вместо обращений к полям позапихает везде в вызовах константы.
Здравствуйте, Erop, Вы писали: E>Идея модели очень простая. Все поля и базы константного объекта -- константны.
E>При учёте этих двух тонкостей ситуация становится уже почти радужной, но только до той поры, пока ты пользуешься совсем простыми моделями константности методов. Если же тебе зачем-то потребуется что-то "непростое", то уже станет тесно. E>Например, если константность метода будет зависеть от "значения" его параметров, то С++ модель константности становится совсем уж обременительной.
Судя по всему есть все — таки разделение в С++ между определяемыми и нативными типами. До сих пор в документации в явном виде этого нигде не встречал. На самом деле это не единственное различие.
Re[2]: Вопрос знатокам стандарта C++
От:
Аноним
Дата:
04.09.07 14:52
Оценка:
const int txt = 3;
int main(int argc, char * argv[])
{
*(const_cast<int*>(&txt)) = 5;
return 0;
}
VS6, debug, AV после запуска. Если убрать const перед int то все ок. Теперь думаем
Здравствуйте, dandy, Вы писали:
D>Судя по всему есть все — таки разделение в С++ между определяемыми и нативными типами. До сих пор в документации в явном виде этого нигде не встречал. На самом деле это не единственное различие.
Ну слово POD поищи, например...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Аноним, Вы писали:
А>Константность объекта это хинт компилятору на то что класс никто не будет изменять.
Не согласен я с этим! Если бы это было так, то ни надо бы было использовать слово const, пусть бы компилятор без хинтов компилировал
const — это хинт программисту!!! Тем и ценен (сравни с volatile, который на самом деле хинт компилятору )
А>Зная это компилятор может например положить глобальный экземпляр класса в read-only memory (а на некоторых платформах даже в ROM записать). Соответственно снятие констанстности и модификация объекта у которогоthis указывает на read-only память потом может обернуться в access violation. А еще константность объекта может указатть компилятору на то что поля-члены никогда не будут изменяться после вызова конструктора. Соответственно он возьмет и вместо обращений к полям позапихает везде в вызовах константы.
Ну это всё скорее про статические объекты страшилки. Но в любом случае зачем такой зловредный хинт компилятору использовать?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, Аноним, Вы писали:
А>>Константность объекта это хинт компилятору на то что класс никто не будет изменять. E>Не согласен я с этим! Если бы это было так, то ни надо бы было использовать слово const, пусть бы компилятор без хинтов компилировал
E>const — это хинт программисту!!! Тем и ценен (сравни с volatile, который на самом деле хинт компилятору )
А>>Зная это компилятор может например положить глобальный экземпляр класса в read-only memory (а на некоторых платформах даже в ROM записать). Соответственно снятие констанстности и модификация объекта у которогоthis указывает на read-only память потом может обернуться в access violation. А еще константность объекта может указатть компилятору на то что поля-члены никогда не будут изменяться после вызова конструктора. Соответственно он возьмет и вместо обращений к полям позапихает везде в вызовах константы.
E>Ну это всё скорее про статические объекты страшилки. Но в любом случае зачем такой зловредный хинт компилятору использовать?
Здравствуйте, Аноним, Вы писали:
А>>Во многих учебниках встречался с утверждениями типа "константный обьект класса изменить невозможно", но в Visual С++ следующий код срабатывает без проблем: А>Знаете, то что код работает у вас здесь и сейчас совершенно не означает то что он будет работать у всех, везде и всегда. Константность объекта это хинт компилятору на то что класс никто не будет изменять. Зная это компилятор может например положить глобальный экземпляр класса в read-only memory (а на некоторых платформах даже в ROM записать). Соответственно снятие констанстности и модификация объекта у которогоthis указывает на read-only память потом может обернуться в access violation. А еще константность объекта может указатть компилятору на то что поля-члены никогда не будут изменяться после вызова конструктора. Соответственно он возьмет и вместо обращений к полям позапихает везде в вызовах константы.
Это только предположения? Вы можете привести факты?