Герб Саттер - Стандарты программирования на С++. 101 правило и рекомендация
- Название:Стандарты программирования на С++. 101 правило и рекомендация
- Автор:
- Жанр:
- Издательство:Издательский дом Вильямс
- Год:2005
- Город:Москва
- ISBN:5-8459-0859-0
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Герб Саттер - Стандарты программирования на С++. 101 правило и рекомендация краткое содержание
Эта книга поможет новичку стать профессионалом, так как в ней представлен сконцентрированный лучший опыт программистов на С++, обобщенный двумя экспертами мирового класса.
Начинающий программист найдет в ней простые и понятные рекомендации для ежедневного использования, подкрепленные примерами их конкретного применения на практике.
Опытные программисты найдут в ней советы и новые рекомендации, которые можно сразу же принять на вооружение. Программисты-профессионалы могут использовать эту книгу как основу для разработки собственных стандартов кодирования, как для себя лично, так и для группы, которой они руководят.
Конечно, книга рассчитана в первую очередь на профессиональных программистов с глубокими знаниями языка, однако она будет полезна любому, кто захочет углубить свои знания в данной области.
Стандарты программирования на С++. 101 правило и рекомендация - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Встроенные версии &&
и ||
сначала вычисляют левую часть выражения, и если она полностью определяет результат ( false
для &&
, true
для ||
), то вычислять правое выражение незачем — и оно гарантированно не будет вычисляться. Таким образом мы используем эту возможность, позволяя корректности правого выражения зависеть от успешного вычисления левого:
Employee* е = TryToGetEmployee();
if (е && e->Manager())
// ...
Корректность этого кода обусловлена тем, что e->Manager()
не будет вычисляться, если e имеет нулевое значение. Это совершенно обычно и корректно — до тех пор, пока не используется перегруженный оператор operator&&
, поскольку в таком случае выражение, включающее &&
, будет следовать правилам функции:
• вызовы функций всегда вычисляют все аргументы до выполнения кода функции;
• порядок вычисления аргументов функций не определен (см. также рекомендацию 31). Давайте рассмотрим модернизированную версию приведенного ранее фрагмента, которая
использует интеллектуальные указатели:
some_smart_ptr е = TryToGetEmployee();
if (е && e->Manager())
// ...
Пусть в этом коде используется перегруженный оператор operator&&
(предоставленный автором some_smart_ptr
или Employee
). Тогда мы получаем код, который для читателя выглядит совершенно корректно, но потенциально может вызвать e->Manager()
при нулевом значении e
.
Некоторый иной код может не привести к аварийному завершению программы, но стать некорректным по другой причине — из-за зависимости от порядка вычислений двух выражений. Результат может оказаться плачевным. Например:
if (DisplayPrompt() && GetLine()) // ...
Если оператор operator&&
переопределен пользователем, то неизвестно, какая из функций — DisplayPrompt
или GetLine
— будет вызвана первой. Программа в результате может ожидать ввода пользователя до того, как будет выведено соответствующее поясняющее приглашение.
Конечно, такой код может заработать при использовании вашего конкретного компилятора и настроек сборки. Но это — очень ненадежно. Компиляторы могут выбрать любой порядок вычислений (и так они и поступают), который сочтут нужным для данного конкретного вызова, принимая во внимание такие факторы, как размер генерируемого кода, доступные регистры, сложность выражений и другие. Так что один и тот же вызов может проявлять себя по-разному в зависимости от версии компилятора, настроек компиляции и даже инструкций, окружающих данный вызов.
Та же ненадежность наблюдается и в случае оператора-запятой. Так же, как и операторы &&
и ||
, встроенный оператор-запятая гарантирует, что выражения будут вычислены слева направо (в отличие от &&
и ||
, здесь всегда вычисляются оба выражения). Пользовательский оператор-запятая не может гарантировать вычислений слева направо, что обычно приводит к удивительным результатам. Например, если в следующем коде используется пользовательский оператор-запятая, то неизвестно, получит ли функция g
аргумент 0 или 1:
int i = 0;
f(i++), g(i); //См. также рекомендацию 31
Пример. Инициализация библиотеки при помощи перегруженного оператора operator,
для последовательности инициализаций. Некоторая библиотека пытается упростить добавление нескольких значений в контейнер за один раз путем перегрузки оператора-запятой. Например, для добавления в vector letters
:
set_cont(letters) += "a", "b";
Все в порядке, пока в один прекрасный день пользователь не напишет:
set_cont(letters) += getstr(), getstr();
// порядок не определен при использовании
// перегруженного оператора ","
Если функция getstr
получает, например, ввод пользователя и он введет строки "с"
и "d"
в указанном порядке, то в действительности строки могут оказаться внесены в любом порядке. Это может оказаться сюрпризом, поскольку при использовании встроенного оператора operator,
такой проблемы не возникает:
string s; s = getstr(), getstr(); // порядок строго определен
// при использовании
// встроенного оператора ","
Исключение — специализированные библиотеки шаблонов для научных вычислений, которые в соответствии с дизайном переопределяют все операторы.
[Dewhurst03] §14 • [Meyers96] §7, §25 • [Murray93] §2.4.3 • [Stroustrup00] §6.2.2
31. Не пишите код, который зависит от порядка вычислений аргументов функции
Порядок вычисления аргументов функции не определен, поэтому никогда не полагайтесь на то, что аргументы будут вычисляться в той или иной очередности.
На начальных этапах развития языка C регистры процессора были драгоценным ресурсом и компиляторы решали трудные задачи эффективного их использования в сложных выражениях высокоуровневых языков. Для того чтобы позволить компилятору генерировать более быстрый код, создатели C дали распределителю регистров дополнительную степень свободы. При вызове функции порядок вычисления ее аргументов оставался неопределенным. Эта аргументация, вероятно, существенно менее важна в настоящее время, но главное, что порядок вычисления аргументов функций в C++ не определен и варьируется от компилятора к компилятору (см. также рекомендацию 30).
В связи с этим необдуманные действия программиста могут привести к большим неприятностям. Рассмотрим следующий код:
void Transmogrify(int, int);
int count = 5;
Transmogrify(++count, ++count); // Порядок вычислений
// неизвестен
Все, что мы можем сказать определенного, — это то, что при входе в тело функции Transmogrify
значение переменной count
будет равно 7 — но мы не можем сказать, какой аргумент будет равен 6, а какой — 7. Эта неопределенность остается и в гораздо менее очевидных случаях, таких как функции, модифицирующие свои аргументы (или некоторое глобальное состояние) в качестве побочного действия:
int Bump(int& x) { return ++x; }
Transmogrify(Bump(count), Bump(count)); // Результат
// неизвестен
Согласно рекомендации 10, следует в первую очередь избегать глобальных и совместно используемых переменных. Но даже если вы благополучно устраните их, некоторый другой код может этого не сделать. Например, некоторые стандартные функции имеют побочные действия (например, strtok
, а также разные перегруженные операторы operator<<
, принимающие в качестве аргумента ostream
).
Рецепт очень прост — использовать именованные объекты для того, чтобы обеспечить порядок вычислений (см. рекомендацию 13):
int bumped = ++count;
Transmogrify(bumped, ++count); // все в порядке
Интервал:
Закладка: