Герб Саттер - Стандарты программирования на С++. 101 правило и рекомендация стр 19.

Шрифт
Фон

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); // все в порядке

Ссылки

[Alexandrescu00c] • [Cline99] §31.03-05 • [Dewhurst03] §14-15 • [Meyers96] §9-10 • [Stroustrup00] §6.2.2, §14.4.1 • [Sutter00] §16 • [Sutter02] §20-21

Проектирование классов и наследование

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

- Бьярн Страуструп (Bjarne Stroustrup)

Какого вида классы предпочитает разрабатывать и строить ваша команда? Почему?

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

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

В следующем разделе мы обратимся к Большой Четверке специальных функций - конструктору по умолчанию, копирующему конструктору, копирующему присваиванию и деструктору.

В этом разделе мы считаем самой важной рекомендацию 33 - "Предпочитайте минимальные классы монолитным".

32. Ясно представляйте, какой вид класса вы создаете

Резюме

Существует большое количество различных видов классов, и следует знать, какой именно класс вы создаете.

Обсуждение

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

Классы-значения (например, std::pair, std::vector) моделируют встроенные типы. Эти классы обладают следующими свойствами.

• Имеют открытые деструктор, копирующий конструктор и присваивание с семантикой значения.

• Не имеют виртуальных функций (включая деструктор).

• Предназначены для использования в качестве конкретных классов, но не в качестве базовых (см. рекомендацию 35).

• Обычно размещаются в стеке или являются непосредственными членами другого класса.

Базовые классы представляют собой строительные блоки иерархии классов. Базовый класс обладает следующими свойствами.

• Имеет деструктор, который является либо открытым и виртуальным, либо защищенным и невиртуальным (см. рекомендацию 50), а также копирующий конструктор и оператор присваивания, не являющиеся открытыми (см. рекомендацию 53).

• Определяет интерфейс посредством виртуальных функций.

• Обычно объекты такого класса создаются динамически в куче как часть объекта производного класса и используются посредством (интеллектуальных) указателей.

Говоря упрощенно, классы свойств представляют собой шаблоны, которые несут информацию о типах. Класс свойств обладает следующими характеристиками.

• Содержит только операторы typedef и статические функции. Класс не имеет модифицируемого состояния или виртуальных функций.

• Обычно объекты данного класса не создаются (конструкторы могут быть заблокированы).

Классы стратегий (обычно шаблоны) являются фрагментами сменного поведения. Классы стратегий обладают следующими свойствами.

• Могут иметь состояния и виртуальные функции, но могут и не иметь их.

• Обычно объекты данного класса не создаются, и он выступает в качестве базового класса или члена другого класса.

Классы исключений представляют собой необычную смесь семантики значений и ссылок. При генерации исключений они передаются по значению, но должны перехватываться по ссылке (см. рекомендацию 73). Классы исключений обладают следующими свойствами.

• Имеют открытый деструктор и конструкторы, не генерирующие исключений (в особенности копирующий конструктор, генерация исключения в котором приводит к завершению работы программы).

• Имеют виртуальные функции и часто реализуют клонирование (см. рекомендацию 54).

• Предпочтительно делать их производными от std::exception.

Вспомогательные классы обычно поддерживают отдельные идиомы (например, RAII - см. рекомендацию 13). Важно, чтобы их корректное использование не было сопряжено с какими-либо трудностями и наоборот - чтобы применять их некорректно было очень трудно (например, см. рекомендацию 53).

Ссылки

[Abrahams01b] • [Alexandrescu00a] • [Alexandrescu00b] • [Alexandrescu01] §3 • [Meyers96] §13 • [Stroustrup00] §8.3.2, §10.3, §14.4.6, §25.1 • [Vandevoorde03] §15

33. Предпочитайте минимальные классы монолитным

Резюме

Разделяй и властвуй: небольшие классы легче писать, тестировать и использовать. Они также применимы в большем количестве ситуаций. Предпочитайте такие небольшие классы, которые воплощают простые концепции, классам, пытающимся реализовать как несколько концепций, так и сложные концепции (см. рекомендации 5 и 6).

Ваша оценка очень важна

0
Шрифт
Фон

Помогите Вашим друзьям узнать о библиотеке