Вопрос знатокам стандарта C++
От: Аноним  
Дата: 04.09.07 12:06
Оценка:
Во многих учебниках встречался с утверждениями типа "константный обьект класса изменить невозможно", но в 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 не соответствует стандарту? Кто — нибудь это знает?
Re: Вопрос знатокам стандарта C++
От: dip_2000 Россия  
Дата: 04.09.07 12:09
Оценка:
А>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 точно стандарту не соответствует но не в этой части
Re: Вопрос знатокам стандарта C++
От: LaptevVV Россия  
Дата: 04.09.07 12:14
Оценка:
Здравствуйте, Аноним, Вы писали:
А>Где ошибка: в учебниках? Или Visual C++ 6.0 не соответствует стандарту? Кто — нибудь это знает?
1. Утверждение стандарта, конечно, правильное... Но естественно, существуют способы обойти это ограничение... Доступ черех указатель- классический хак... Но есть и стандартный способ... В частности обратите внимание на слово mutable, позоляющее стандартным способом изменять константный объект... На РСДН уже неоднократно обсуждался вопрос о концептуальной и физической константности — поищите в поиске
2. VC6 очень плохо соответствует стандарту — старый очень, создавался ДО принятия официального стандарта...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[2]: Вопрос знатокам стандарта C++
От: dandy  
Дата: 04.09.07 12:16
Оценка:
Здравствуйте, dip_2000, Вы писали:

_>const_cast нужен как раз для "снятия" константности, с объекта который константным не является изначально. все ок.


Спасибо, но это мне известно (можно догадаться по коду). Значит проблема в учебниках.
Может быть имеет смысл их исправить?
Re[2]: Вопрос знатокам стандарта C++
От: dandy  
Дата: 04.09.07 12:23
Оценка:
Здравствуйте, LaptevVV, Вы писали:

LVV>1. Утверждение стандарта, конечно, правильное... Но естественно, существуют способы обойти это ограничение... Доступ черех указатель- классический хак... Но есть и стандартный способ... В частности обратите внимание на слово mutable, позоляющее стандартным способом изменять константный объект... На РСДН уже неоднократно обсуждался вопрос о концептуальной и физической константности — поищите в поиске

LVV>2. VC6 очень плохо соответствует стандарту — старый очень, создавался ДО принятия официального стандарта...

Спасибо за наводку. Но есть некое несоответствие при приведении типов через указатель — в этом случае оно срабатывает только с производными типами (классами). С натуральными C++ типами это не работает. В чем тут подвох? Может быть стоит стандарт почитать?
Re: Вопрос знатокам стандарта C++
От: igna Россия  
Дата: 04.09.07 12:26
Оценка:
Здравствуйте, Аноним, Вы писали:

А>    A * pA = const_cast<A *>(&a);
А>    A& ra = *pA;


Можно проще:

    A& ra = const_cast<A&>(a);


(Прошу прощения, замечание не по теме)
Re: Вопрос знатокам стандарта C++
От: Bell Россия  
Дата: 04.09.07 12:28
Оценка: 3 (2)
Здравствуйте, Аноним, Вы писали:

А>Где ошибка: в учебниках? Или 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 const

   int i = 2; // not cv-qualified
   const 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 const

   int* ip;
   ip = const_cast<int*>(cip); // cast needed to convert const int* to int*
   *ip = 4; // defined: *ip points to i, a non-const object

   const int* ciq = new const int (3); // initialized as required
   int* 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]


Возвращаясь к исходному примеру:
A a;
cout << a.x  << endl;
f(a);
cout << a.x  << endl;

Этот кусок well-formed, т.к. константность в f снимается с объекта, который константой не явялется.

const A cA;
cout << cA.x  << endl;
f(cA);
cout << cA.x  << endl;

А вот этот кусок содержит неопределенное поведение, т.к. в данном случае имеет место модификация "настоящей" константы.
То, что программа выдает "правильные" результаты (до поры — до времени) — частный случай неопределенного поведения
Любите книгу — источник знаний (с) М.Горький
Re[3]: Вопрос знатокам стандарта C++
От: igna Россия  
Дата: 04.09.07 12:29
Оценка:
Здравствуйте, dandy, Вы писали:

D>Но есть некое несоответствие при приведении типов через указатель — в этом случае оно срабатывает только с производными типами (классами). С натуральными C++ типами это не работает.


Приведи пример, где не работает.

D>Может быть стоит стандарт почитать?


Вряд ли, по моему пока не стоит.
Re[2]: Вопрос знатокам стандарта C++
От: dandy  
Дата: 04.09.07 12:31
Оценка:
Здравствуйте, igna, Вы писали:

I>Можно проще:


I>
I>    A& ra = const_cast<A&>(a);
I>


I>(Прошу прощения, замечание не по теме)


И правда
Re: Вопрос знатокам стандарта C++
От: Кодт Россия  
Дата: 04.09.07 12:54
Оценка: 1 (1) :))
Здравствуйте, <Аноним>, Вы писали:

Это из серии "Как выстрелить себе в ногу"
— с помощью 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; // 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 до необычной арифметики и логики).
... << RSDN@Home 1.2.0 alpha rev. 655>>
Перекуём баги на фичи!
Re: Вопрос знатокам стандарта C++
От: alzt  
Дата: 04.09.07 13:36
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Во многих учебниках встречался с утверждениями типа "константный обьект класса изменить невозможно", но в Visual С++ следующий код срабатывает без проблем:


А>Где ошибка: в учебниках? Или Visual C++ 6.0 не соответствует стандарту? Кто — нибудь это знает?


Скорее это учебники для начинающих. "константный обьект класса изменить невозможно", но если очень хочется, то можно.
Слово const больше нужно для программистов. Чтобы код читался легче, был более документированным.
const_cast нужен главным образом, для вызова из константных методов старых неконстантных функций, которые не меняют свой аргумент.
Изменить конечно можно, но это подобно этому:
int sum(int a,int b)
{
   return a-b;
}
Re: ИМХА про константность
От: Erop Россия  
Дата: 04.09.07 13:40
Оценка: 96 (9) +2
Здравствуйте, Аноним, Вы писали:

А>Во многих учебниках встречался с утверждениями типа "константный обьект класса изменить невозможно",..


Я не знаток и не учебник, но попробую поделиться некоторыми соображениями по вопросу.
Общий смысл понятия константность сводится к тому, что мы заявляем, что некое "значение" объекта не может измениться.
Тут возникает вопрос, что такое "значение". Это собственно то, что объект представляет. Если объект представляет число -- значит "значение" -- это число. Например объект "рациональное число" в состояниях {1 / 2 } и в состоянии { 2 / 4 } представляет одно и то же "значение" 0.5.
Объект "строка" представляет значение "последовательность символов", при этом где именно хранится эта последовательность символов и как в "значение" строки не входит.
Я не хочу углубляться в понятие "значение" объекта, так как это уведёт очень далеко от понятия константонсти.
Главное понимать, что в состоянии объекта есть как бы две грани. Одна -- это подробности реализации, которые пользователю объекта не интересны и некое "значение", ради которого объект собственно и используется.
Во всяком случае так обстоят дела в хороших программах.

Так вот, часто так бывает, что при программировании полезно указать, что "значение" какого-то объекта не может измениться в течении работы какого-то алгоритма.
Тогда этот объект изнутри этого алгоритма представляют как константный. При этом у константного объекта можно вызывать только константные методы.
Это просто некий сервис, предоставляемый языком.
Если ты обозначил какой-то метод константным, то ты сообщаешь пользователю класса, что этот метод не меняет "значение" класса.

В принципе, за одним небольшим исключением, ты волен реализовывать константность метода (то есть неизменность "значения" объекта) как тебе наравится. Но для удобства, C++ предлагет некоторую модель константности, которая обычно интуитивно понятна почти во всём.

Идея модели очень простая. Все поля и базы константного объекта -- константны.
Тут есть две тонкости.
Тонкость 1 состоит в том, что если такое константное поле является указателем, то менять нельзя сам указатель (например нельзя начать ссылаться на другой объект), а вот сам указуемый объект менять как раз можно. При этом довольно часто в "значение" объемлещего класса входит не "значение" указателя, а "значение" указуемого объекта.
Так что заботу о сохранении семантики константности С++ в случае поля-указаетля перекладывает на программиста.
Тонкость 2 состоит в том, что в объекте может быть поле "значение" которого не входит в "значение" объекта.
То есть вполне законно можно менять "значение" такого поля даже из константного (то есть не меняющего "значение" объекта) метода. Для этого в C++ есть ключевое слово mutable, которым маркируются такие поля.

При учёте этих двух тонкостей ситуация становится уже почти радужной, но только до той поры, пока ты пользуешься совсем простыми моделями константности методов. Если же тебе зачем-то потребуется что-то "непростое", то уже станет тесно.
Например, если константность метода будет зависеть от "значения" его параметров, то С++ модель константности становится совсем уж обременительной.

Именно для таких случаев и используется const_cast, по идее.
То есть может быть написано что-то типа (MFC-style):
class CMyClass {
public:
    Save( CArchive& arc ) const 
        { assert( arc.IsStoring(); const_cast<CMyClass *>( this )->Serialize( arc ); }
    Load( CArchive& arc ) 
        { assert( arc.IsLoading(); Serialize( arc ); }
protected:
    virtual Serialize( CArchive& );  // Тут собственно реализация сидит
};


Ну а теперь настало время вернуться к тому самом "одному небольшому исключению".
Исключние наступает в случае, когда на сцену выступают константные объекты стандартных типов (числа, указатели и т. п.). Так как неизменность их "значений" реализует и гарантирует компилятор, то он может использовать информацию о их константности для оптимизации и реализации этих объектов и работы с ними. Поэтому, если ты что-то говоришь о константности каких-то объектов, то в конце концов ты обычно что-то говоришь о константоности находящихся в них в виде явных или неявных полей объектов стандартных типов. И если ты тут будешь слишком вольно обращаться с константностью, то ты можешь нарваться.
Это всего лишь вопрос в том как ограничить произвол программиста и компилятора в области обращения с константностью стандартных типов. В С++ принято следующее компромиссное соглашение на эту тему.
Константность бывает как бы " истинная" и "предписанная".
Истинной константой является переменная, которую создали как константу. Если это переменная стандартного типа, то компилятор может с ней вытворять много странного, скажем может её вообще не заводить, пока ты не используешь её адрес, а использовать всюду непосредственно её "значение", а может завести в read-only сегменте памяти и отмапировать её значение прямо из секции данных исполняемого файла, а может и ещё чего учудить.
Если это поле какой-то структуры с "истинной" константностью, то компилятор всё равно может хаккерить, хотя и меньше. Во всякмо случае не использовать значение реально расположенное в памяти, и расположить в read-only памяти может запросто. Короче говоря в отношении работы с "истинными констанатми" у компилятора есть дополнительные степени свободы. Правда эта свобода не распространяется на mutable поля даже "истинных констант".
В отличии от "истинной" константности, есть ещё и константность "предписанная". Она возникает, когда мы начинаем обращаться с неконстантным объектом, как с константным. Например имея к нему доступ через константную ссылку. Тогда руки у компилятора связаны и мы можем как угодно мухлевать с const_cast, не рискуя нарваться на последствия "излишней изобретательности" компилятора.

Так что снимать "истинную" константность опасно, потому что есть опасность нарваться на глюки, а "предписанную" опасно потому, что можно нарушить семантику константности объекта (то есть нарушить обещание не менять "значение" объекта).
Но первичным, тем не менее, тут является именно обещание не изменять "значение" объекта, а не какие-то там правила. Другое дело, что правила С++ обычно сразу подходят в большинсте случаев и нет нужды изобретать какую-то хитрую схему управления константностью объекта.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[2]: Вопрос знатокам стандарта C++
От: dandy  
Дата: 04.09.07 14:23
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, <Аноним>, Вы писали:


К>Это из серии "Как выстрелить себе в ногу"

Это зачем?

К>- с помощью 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. А еще константность объекта может указатть компилятору на то что поля-члены никогда не будут изменяться после вызова конструктора. Соответственно он возьмет и вместо обращений к полям позапихает везде в вызовах константы.
Re[2]: ИМХА про константность
От: dandy  
Дата: 04.09.07 14:42
Оценка:
Здравствуйте, 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 то все ок. Теперь думаем
Re[3]: ИМХА про константность
От: Erop Россия  
Дата: 04.09.07 14:56
Оценка:
Здравствуйте, dandy, Вы писали:

D>Судя по всему есть все — таки разделение в С++ между определяемыми и нативными типами. До сих пор в документации в явном виде этого нигде не встречал. На самом деле это не единственное различие.


Ну слово POD поищи, например...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[2]: Вопрос знатокам стандарта C++
От: Erop Россия  
Дата: 04.09.07 15:10
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Константность объекта это хинт компилятору на то что класс никто не будет изменять.

Не согласен я с этим! Если бы это было так, то ни надо бы было использовать слово const, пусть бы компилятор без хинтов компилировал

const — это хинт программисту!!! Тем и ценен (сравни с volatile, который на самом деле хинт компилятору )

А>Зная это компилятор может например положить глобальный экземпляр класса в read-only memory (а на некоторых платформах даже в ROM записать). Соответственно снятие констанстности и модификация объекта у которогоthis указывает на read-only память потом может обернуться в access violation. А еще константность объекта может указатть компилятору на то что поля-члены никогда не будут изменяться после вызова конструктора. Соответственно он возьмет и вместо обращений к полям позапихает везде в вызовах константы.


Ну это всё скорее про статические объекты страшилки. Но в любом случае зачем такой зловредный хинт компилятору использовать?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[3]: Вопрос знатокам стандарта C++
От: dandy  
Дата: 04.09.07 15:12
Оценка: +1 :)
Здравствуйте, Erop, Вы писали:

E>Здравствуйте, Аноним, Вы писали:


А>>Константность объекта это хинт компилятору на то что класс никто не будет изменять.

E>Не согласен я с этим! Если бы это было так, то ни надо бы было использовать слово const, пусть бы компилятор без хинтов компилировал

E>const — это хинт программисту!!! Тем и ценен (сравни с volatile, который на самом деле хинт компилятору )


А>>Зная это компилятор может например положить глобальный экземпляр класса в read-only memory (а на некоторых платформах даже в ROM записать). Соответственно снятие констанстности и модификация объекта у которогоthis указывает на read-only память потом может обернуться в access violation. А еще константность объекта может указатть компилятору на то что поля-члены никогда не будут изменяться после вызова конструктора. Соответственно он возьмет и вместо обращений к полям позапихает везде в вызовах константы.


E>Ну это всё скорее про статические объекты страшилки. Но в любом случае зачем такой зловредный хинт компилятору использовать?


Согласен
Re[2]: Вопрос знатокам стандарта C++
От: dandy  
Дата: 04.09.07 15:15
Оценка:
Здравствуйте, Аноним, Вы писали:

А>>Во многих учебниках встречался с утверждениями типа "константный обьект класса изменить невозможно", но в Visual С++ следующий код срабатывает без проблем:

А>Знаете, то что код работает у вас здесь и сейчас совершенно не означает то что он будет работать у всех, везде и всегда. Константность объекта это хинт компилятору на то что класс никто не будет изменять. Зная это компилятор может например положить глобальный экземпляр класса в read-only memory (а на некоторых платформах даже в ROM записать). Соответственно снятие констанстности и модификация объекта у которогоthis указывает на read-only память потом может обернуться в access violation. А еще константность объекта может указатть компилятору на то что поля-члены никогда не будут изменяться после вызова конструктора. Соответственно он возьмет и вместо обращений к полям позапихает везде в вызовах константы.

Это только предположения? Вы можете привести факты?
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.