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

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



ссылка на сообщение  Отправлено: 28.07.12 15:34. Заголовок: Почему f( a++ ); и f( a ); a++; не одно и то же


Недавно на одном форуме в дискуссии со мной один самопровозглашенный "эксперт" по С++, когда ему нечего было сказать, сослался на Герба Саттера, как непререкаемого авторитета по С++. Но действительно ли те, кто пишет книжки по С++, такие уж непререкаемые авторитеты? Я решил развеять этот миф.
В качестве примера рассмотрим отрывок из книги Герба Саттера "Решение сложных задач на С++". В этом отрывке автор предлагает читателям ответить на вопрос, является ли следующий код эквивалентным

1)
f( a++);

2)
f( a ); a++;


И затем, выждав паузу от произведенного эффекта на неискушенного читателя, сам же отвечает на свой вопрос. Сначала он сообщает банальные вещи,что если в функции f произойдет исключение,то естественно во втором случае приращение переменной a не произойдет. Но это было лишь предисловие. Это банально, и не ради этого писался этот раздел книги. Главным же пуантом этого раздела является следующий пример Герба.

Допустим имеется список

std::list<int> l;


и выполняется операция удаления из списка элемента по заданному итератору, то есть a имеет тип std::list<int>::iterator.

И Герб предлагает сравнить код:

l.erase( a++ );


и

l.erase( a ); 
a++;

После этого он делает нравоучительное пояснение. В первом случае новое значение итератора в переменной a будет действивтельным, так как оно будет вычислено до удаления текущего элемента списка. Во втором же случае новое значение итератора не является действительным, так как мы пытаемся инкрементировать итератор, который уже стал недействительным после удаления элемента из списка.

Казалось бы все верно. Но так ли это на самом деле?! А на самом деле и первый код, и второй код - некорректные! Правильно вообще не делать таким образом, как указано в обоих случаях. Во-первых, итератор a может быть "последним" итератором, то есть указывать за границу списка. Это вполне корректно передать функции erase() итератор l.end(). Функция просто сравнивает его с итератором end(), и если он оказывается равным, то ничего не удаляет из списка. Но в этом случае инкрементирование этого итератора, что в первом случае,что во втором случае окажется неверным!

Но и это еще не все! Давайте слегда изменим пример и вместо списка рассмотрим вектор и аналогичный метод erase(). Что тогда изменится? А изменится то, что оба варианта вызова метода будут некорректны!

std::vector<int> v; 
/* заполняем каким-нибудь образом вектор *?

std::vector<int>::iterator a;

/* также присваиваем какое-нибудь значение итератору */

v.erase( a++);



или, заменив последнюю строчку

v.erase( a ); 
a++;


То есть в обоих случаях значение итератора будет неверным! Ведь при удалении элемента вектора, элементы, стоящие после удаленного элемента, сдвигаются на одну позицию влево, занимая позицию удаленного элемента.

Поэтому и первый код, предложенный Саттером, и второй код - оба они не корректные! Правильно функцию erase вызывать следующим образом, если после ее вызова мы хотим,чтобы итератор указывал на следующий элемент после удаленного:

a = l.erase( a ); // для списка


или

a = v.erase( a ); // для вектора


То есть пример Герба Саттера и его рассуждения вообще не состоятельны! Так как можно долго рассуждать про некорректный код!:)

Так ы чем же на самом деле существует отличие между вызовами f( a++ ); и f( a ); a++;? если не принимать во внимание заведомо некорректный код?

А разница в том, что перед вызовом функции существует точка последовательности ( sequence point ) вычисления выражений.То есть все побочные эффекты до вызова функции реализуются! Вот именно это и важно, и о чем умалчивает Герб Саттер. Вместо этого важного момента он приводит заведомо некорректный код!

Так в чем различие между двумя фрагметами кода f( a++ ); и f( a ); a++;?!

Это различие станет ясным, если рассмотреть следующий код:

#include   <iostream> 

int a = 10;

void f( int a )
{
std::cout <<"a = " << a << std::endl;
std::cout << "::a = " << ::a << std::endl;
}

int main()
{
f( a++ );
}



Когда мы вызываем функцию f, инкрементируя глобальную переменную a, то результат ее изменения уже будет виден в теле функции f, так как побочный эффект будет реализован до вызова функции. Поэтому в качестве вывода мы получим результат, что a = 10 и ::a = 11

Ежели мы делали вызов как f( a ); a++;, то вывод был бы другим.

В этом и состоит на самом деле принципиальное различие между двумя этими вызовами, а не в том, что Герб Саттер указывает заведомо некорректный код!:)

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


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

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