On-line: гостей 0. Всего: 0 [подробнее..]
Программисты всех стран, объединяйтесь!

АвторСообщение



ссылка на сообщение  Отправлено: 06.08.12 02:54. Заголовок: Баги компиляторов MS VC++ 2010 и GCC 4,7.1: Магия С++: когда константа перестает быть константой


Заголовок этой темы может натолкнуть на мысль, что речь пойдет об операторе приведения типов const_cast. Мол, какие проблемы: примени const_cast к константному объекту и тем самым уберешь константность. Вот и вся магия С++!

На самом деле эта тема не посвящена оператору приведения типов const_cast, как это может изначально показаться из заглавия темы. Разговор пойдет совсем о другой "магии С++", которая порой ставит в тупик даже опытных программистов.

Но раз const_cast все же упомянут, то он заслуживает, чтобы о нем было сказано несколько слов.

Чаще всего оператор приведения типов const_cast используется для удаления константности у переменной, когда заранее известно, что объект, который она обозначает, не определен на самом деле константным.

Попытка удалить константность у константного объекта с целью изменить его может привести к печальным последствиям, так как такое поведение неопределено. Вот что говорит по этому поводу стандарт С++ в параграфе №4 раздела "7.1.6.1 The cv-qualifiers":


 цитата:
"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."



Поэтому не пытайтесь это делать.

Тем не менее это не означает, что оператор const_cast не находит применения. Примером использования оператора приведения типов const_cast может служить описание из книги Скотта Майерса "Эффективное использование С++. 55 верных советов" В "Правиле 3" в подразделе озаглавленном "Как избежать дублирование в константных и неконстантных функциях-членах" Майерс демонстрирует, как можно не дублировать код в реализации оператор-функций индексации. Обычно в классах объявляются две такие функции. Одна из них возвращает константную ссылку на объект и объявляется константной, другая же возвращает неконстантную ссылку на объект, тем самым позволяя менть значение объекта.
Обычно эти две функции за исключением квалификаторов const имеют одинаковый код, поэтому одну из функций можно реализовать простым вызовом другой функции. Естественно в этом случае придется применить приведение типов const_cast, чтобы убрать константность.

Допустим уже имеется определение константной оператор-функции индексирования для некоторого объекта типа char с именем text в класса SomeClass: (символьного массива)

const char & operator []( std::size_t pos ) const 
{
/* возмодно некоторые необходимые операторы *.
return ( text[position] );
}

Тогда неконстантную оператор-функцию можно реализовать просто через вызов константной оператор-функции:

char & opertaor []( std::size_t pos ) 
{
return ( const_cast<char &>( static_cast<const SomeClass &>( *this )[position] ) );
}


В этой реализации неконстантной оператор-функции оператор приведения типов const_cast относится к возвращаемому типу значения функции, чтобы исключить константность, которая присутствует у возвращаемого типа значения константной оператор-функции, которая вызывается.
static_cast, напротив, применяется к исходному объекту, чтобы добавить ему константность, так как константную функцию класса можно вызывать только для константных объектов.

Вот и весь фокус реализации неконстантной оператор-функции через вызов ее близнеца - константной оператор-функции.

Отмечу, что Стандарт С++ запрещает использовать static_cast для удаления констатности объяекта. Вот соответствующая выдержка из параграфа №1 раздела стандарта С++ 5.2.9 Static cast


 цитата:
The static_cast operator shall not cast away constness



Ради справедливости отмечу, что в исходном примере static_cast используется не для удаления константности объекта, а для противоположной по значению операции: придания константности объекту.

На этом можно поставить точку в разговоре об операторе приведения типов const_cast, так как главный вопрос темы, та магия С++, о которой я хочу рассказать, относится совсем не к этому оператору.



Спасибо: 0 
ПрофильЦитата Ответить
Ответов - 5 [только новые]





ссылка на сообщение  Отправлено: 06.08.12 22:26. Заголовок: Если в определении к..


Если в определении класса вам нужно использовать некоторые константы, то вы можете воспользоваться объявлением статических констант внутри класса, присвоив им при объявлении требуемые значения. Такая инициализация статических констант при их объявлении допустима для целочисленных, или имеющих тип перечисления констант. Об этой предоставляемой возможности языка С++ говорится в парраграфе №3 раздела 9.4.2 Static data members стандарта С++:


 цитата:
3 If a non-volatile const static data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment expression is a constant expression (5.19).



Итак, допустим, в вашем классе имеются объявления двух статических констант типа int, которые условно можно назвать min и max, и есть две статические функции, которые возвращают тзначения этих констант. Ниже приведен примерный код, демонстрирующий сказанное.

 #include <iostream> 

class A
{
public:
static int get_min() { return ( min ); }
static int get_max() { return ( max ); }
private:
static const int min = 0;
static const int max = 100;
};


int main()
{
std::cout << "A::min = " << A::get_min() << std::endl;
std::cout << "A::max = " << A::get_max() << std::endl;

return ( 0 );
}


Этот код должен компилироваться любым компилятором, который поддерживает стандарт С++, начиная со стандарта С++ 2003 года.

Вы довольны, ваш код компилируется, объявление класса достаточно наглядное и компактное, так как значения констант заданы внутри класса, и вам нет необходимости их определять в каком-нибудь отдельном программном модуле. Все определения класса могут распологаться в одном заголовочном фвйле. Поэтому если надо будет делать какие-то изменения в определении класса, то не надо будет искать определения его компонент, разбросанных по многочисленным файлам.

Предположим, что свой проект вы собираете с помощью компилятора MS VC++ 2010.

Через некоторое время работы с проектом вам пришла замечательная идея написать всего лишь одну функцию доступа к вашим константам, в которой выбор требуемой константы будет осуществляться с помощью параметра функции и условного (тернарного) оператора. Для простоты этот параметр будет иметь тип bool. Если в качестве аргумента при вызове функции указывается значение false, то возвращается минимум, а если true, то возвращается максимум.

Вот как будет выглядеть после проделанных изменений код вашей программы:

 #include <iostream> 

class A
{
public:
static int get_limit( bool bound ) { return ( ( bound ) ? max : min ); }
private:
static const int min = 0;
static const int max = 100;
};


int main()
{
std::cout << "A::min = " << A::get_limit( false ) << std::endl;
std::cout << "A::max = " << A::get_limit( true ) << std::endl;

return ( 0 );
}


Вы протестировали свою модифицированную программу с помощью MS VC++ 2010, убедились в ее работоспособности и передали в промышленную эксплуатацию.

Все было хорошо до тех пор, пока вам не потребовалось перенести свою программу на другую платформу, где используется компилятор GCC 4.7.1.

Так как ваша программа достаточно простая, и вы убедились, что она успешно работает, будучи скомпилированной компилятором MS VC++ 2010, и никаких "подводных камней" в ее коде вы не усматриваете, то уверены, что перенос на новую платформу вашей программы займет считанные минуты - время, которое потребуется на ее компиляцию компилятором GCC 4.7.1.

Итак, вы запускаете свою программу на компиляцию компилятором GCC 4.7.1, будучи совершенно спокойным и уверенным в успехе, и...о чудо, компилятор GCC 4.7.1 неожиданно для вас сообщает об ошибках компиляции!


 цитата:
undefined reference to `A::max
undefined reference to `A::min



Вы смотрите в прострации на эти соообщения об ошибках и не верите своим глазам! Вы же ничего не меняли в программе, даже не прикосались к ее исходному коду. Почему компилятор говорит, что A::min и A::max не определены, когда, вот, они перед вашими глазами присутствуют в вашем классе?! Вы думаете, что может быть случайно задели какую-то клавишу, которая привела к опечатке, и заново аккуратно набираете в классе имена этих констант. Пускаете программу на компиляцию, но ничего не меняется. Компилятор настойчиво сообщает об одних и тех же ошибках компиляции, что имена A::min и A::max.не определены в программе.

Никакого иного объяснения в вашу голову не приходит кроме заключения о том, что компилятор GCC 4.7.1 имеет баг! Или... может быть это MS VC++ 2010 имеет баг?!

Вы долго смотрите на свой код, и у вас создается такое впечатление, что совершилась какая-то необъяснимая магия, как будто некий фокусник у вас на глазах украл ваши константы, и как это произошло, вы объяснить не в состоянии.


Спасибо: 0 
ПрофильЦитата Ответить



ссылка на сообщение  Отправлено: 07.08.12 01:22. Заголовок: Возникает три вопрос..


Возникает три вопроса.
1. Имеет ли место баг компилятора GCC 4.7.1?
2. Имеет ли место баг компилятора MS VC++ 2010?
3. Как повлияло использование условного (тернарного) оператора на поведение компиляторов, ведь первоначальная версия класса без использования тернарного оператора успешно компилировалась обоими компиляторами?

Очевидно, что следует начать с ответа на последний третий вопрос. Из него будет ясно, действительно ли какой-либо из указанных компиляторов имеет баг, или может быть здесь, к примеру, имеет место тот случай, когда в результате некорректного использования конструкций языка, поведение программы становится неопредленным. Правда, не понятно, что в этом простом коде является некорректным использованием конструкций языка?!




Спасибо: 0 
ПрофильЦитата Ответить



ссылка на сообщение  Отправлено: 07.08.12 01:50. Заголовок: Не стоит конечно тер..


Не стоит конечно терять самообладание и становиться мнительным, стараясь найти в каждой простой конструкции "двойное дно". Условный оператор в функции get_limit использован совершенно корректно. Тогда в чем же дело?

Обратимся к тексту сообщений об ошибке компилятора GCC 4.7.1. Он гласит, что имеет место ссылка на неопределенные имена A::min и A::max. Почему бы тогда не определить их таким же образом, как определяются статические члены данных классов?

Измененный код программы с включением дополнительных определений статических переменных A::min и A::max будет выглядеть следующим образом:

 #include <iostream> 

class A
{
public:
static int get_limit( bool bound ) { return ( ( bound ) ? max : min ); }
private:
static const int min = 0;
static const int max = 100;
};

const int A::min;
const int A::max;

int main()
{
std::cout << "A::min = " << A::get_limit( false ) << std::endl;
std::cout << "A::max = " << A::get_limit( true ) << std::endl;

return ( 0 );
}


Запускаем код на компиляцию с помощью компилятора GCC 4.7.1, и к радости убеждаемся, что код успешно компилируется и выполняется.

Но почему в первоначальной программе без дополнительного определения статических переменных A::min и A::max код успешно компилировался обоими компиляторами? Значит все-таки дело именно в условном операторе?

Да, это так. Осталось только выяснить, почему использование условного оператора в этой простой программе изменило семантику конструкцмй языка С++.



Спасибо: 0 
ПрофильЦитата Ответить



ссылка на сообщение  Отправлено: 07.08.12 11:15. Заголовок: Обратимся снова к па..


Обратимся снова к параграфу №3 раздела 9.4.2 Static data members стандарта С++. Там есть важное дополнение:


 цитата:
The member shall still be defined in a namespace scope if it is odr-used (3.2) in the program



В параграфе №2 раздела 3.2 One definition rule дается пояснение того, что означает odr-used:


 цитата:
A variable whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied.



Рассмотрим функцию get_limit, слегка изменив ее тело посредством добавления ссылки на результат выполнения условного оператора.

 static int get_limit( bool bound )  
{
const int &r = ( bound ) ? max : min;
return ( r );
}


Чтобы проинициализировать константную ссылку r, она должна ссылаться на действительный объект. Об этом говорится в параграфе №5 раздела 8.3.2 References стандарт С++:


 цитата:
A reference shall be initialized to refer to a valid object or function.



Теперь процитируем параграф №4 раздела стандарта об условном операторе 5.16 Conditional operator:


 цитата:
4 If the second and third operands are glvalues of the same value category and have the same type, the result is of that type and value category



Итак, чтобы инициализировать ссылку в функции get_limit обе переменные A::min и A::max должны представлять собой действительные объекты, а потому следует их определить в пространстве имен, внутри которого содержится объявление класса.
Даже если убрать введенную дополнительно промежуточную ссылку r из функции и вернуться к ее первоначальному виду, все равно при выполнении условного оператора не происходит немедленного преобразования lvalue в rvalue, как того требует параграф №2 раздела 3.2 One definition rule для константных выражений, которые не являются odr-used. Условный оператор в С++ возвращает ссылку для данного случая, а потому для этой ссылки должен существовать действительный объект.

В Комитет по стандартизации было направлено предложение по изменению стандарта, которое позволяло бы для констант, используемых в условном операторе, оставаться не odr-used. Это предложение имеет номер 712. и озаглавлено как Are integer constant operands of a conditional-expression "used?" Можно почитать об этом предложении, поданным в Комитет по стандартизации, по следующей ссылке
Лично я против того, чтобы вносили данное изменение в стандарт, так как это нарушает различие в семантике условного оператора в С++ по сравнению с С.

Из рассмотренного материала теперь легко можно сделать вывод, что именно компилятор MS VC++ 2010 содержит баг. Он не должен был компилировать код, а должен был потребовать явного определения статических констант класса.

Вы возможно уже заметили, что в заголовке этой темы говорится о багах обоих компиляторов: и MS VC++ 2010, и GCC 4.7.1. Так где же баг компилятора GCC 4.7.1?
Здесь имелся в виду баг, относящийся к другой синтаксической конструкции, но также тесно связанной с объявлением статических переменных в классе.
Согласно параграфу №4 уже ранее цитируемого раздела стандарта С++ 9.4.2 Static data members


 цитата:
"Unnamed classes and classes contained directly or indirectly within unnamed classes shall not contain static data members."



Однако если попробовать скмопилировать следующий код

 struct  
{
const static int i = 10;
int a[ i ];
} s;


int main()
{
}


то он успешно скомпилируется обоими компиляторами: и MS VC++ 2010, и GCC 4.7.1, чего не должно быть, как это следует из процитированного положения стандарта С++.

Лично я не вижу причин, почему нельзя использовать статические константные члены данных в неименованных классах, если эти статические константные члены данных не являются odr-used, то есть они еще на этапе компиляции заменяются своими значениями. Поэтому я обратил внимание Комитета по стандартизации, что этот параграф стандарта следует изменить, разрешив в неименованных классах использовать объявления статических константных членов данных, если они не являются odr-used.


Спасибо: 0 
ПрофильЦитата Ответить



ссылка на сообщение  Отправлено: 08.08.12 17:43. Заголовок: В заключении хотел б..


В заключение хотел бы обратить внимание на допускаемый многими программистами ляп, когда они пытаются объявить константный статический объект типа double и инициализировать его во время объявления внутри класса. Например,

 struct A 
{
const static double middle = 50.0;
};


Увы, этого делать нельзя, так как только константные статические объекты, имеющие либо целочисленный тип, либо тип перечисления, можно инициализировать при их объявлении внутри класса. Поэтому любой компилятор, удовлетворяющий стандарту С++, должен выдать сообщение об ошибке для данного объявления структуры A.

Тем не менее есть очень простой выход из данной ситуации. Нужно просто статический объект, имеющий тип числа с плавающей запятой, объявить не с квалификатором const, а со спецификатором constexpr, и тогда этот объект можно инициализировать при объявлении внутри класса.

 struct A 
{
constexpr static double middle = 50.0;
};


Этот код будет успешно компилироваться, если компилятор поддерживает спецификатор constexpr.

Это еще одна магия языка С++!

Спасибо: 0 
ПрофильЦитата Ответить
Ответ:
1 2 3 4 5 6 7 8 9
большой шрифт малый шрифт надстрочный подстрочный заголовок большой заголовок видео с youtube.com картинка из интернета картинка с компьютера ссылка файл с компьютера русская клавиатура транслитератор  цитата  кавычки моноширинный шрифт моноширинный шрифт горизонтальная линия отступ точка LI бегущая строка оффтопик свернутый текст

показывать это сообщение только модераторам
не делать ссылки активными
Имя, пароль:      зарегистрироваться    
Тему читают:
- участник сейчас на форуме
- участник вне форума
Все даты в формате GMT  3 час. Хитов сегодня: 2
Права: смайлы да, картинки да, шрифты да, голосования нет
аватары да, автозамена ссылок вкл, премодерация откл, правка нет