Классы исключений представляют собой необычную смесь семантики значений и ссылок. При генерации исключений они передаются по значению, но должны перехватываться по ссылке (см. рекомендацию 73). Классы исключений обладают следующими свойствами.
• Имеют открытый деструктор и конструкторы, не генерирующие исключений (в особенности копирующий конструктор, генерация исключения в котором приводит к завершению работы программы).
• Имеют виртуальные функции и часто реализуют клонирование (см. рекомендацию 54).
• Предпочтительно делать их производными от std::exception
.
Вспомогательные классы обычно поддерживают отдельные идиомы (например, RAII — см. рекомендацию 13). Важно, чтобы их корректное использование не было сопряжено с какими-либо трудностями и наоборот — чтобы применять их некорректно было очень трудно (например, см. рекомендацию 53).
33. Предпочитайте минимальные классы монолитным
Разделяй и властвуй: небольшие классы легче писать, тестировать и использовать. Они также применимы в большем количестве ситуаций. Предпочитайте такие небольшие классы, которые воплощают простые концепции, классам, пытающимся реализовать как несколько концепций, так и сложные концепции (см. рекомендации 5 и 6).
Разработка больших причудливых классов — типичная ошибка новичка в объектно-ориентированном проектировании. Перспектива иметь класс, который предоставляет полную и сложную функциональность "в одном флаконе", может оказаться очень привлекательной. Однако подход, состоящий в разработке небольших, минимальных классов, которые легко комбинировать, на практике по ряду причин оказывается более успешен для систем любого размера и сложности.
• Минимальный класс воплощает одну концепцию на соответствующем уровне детализации. Монолитный класс обычно включает несколько отдельных концепций, и использование только одной из них влечет за собой излишние накладные расходы (см. рекомендации 5 и 11).
• Минимальный класс легче понять и проще использовать (в том числе повторно).
• Минимальный класс проще в употреблении. Монолитный класс часто должен использоваться как большое неделимое целое. Например, монолитный класс Matrix
может попытаться реализовать и использовать экзотическую функциональность — такую как вычисление собственных значений матрицы — даже если большинству пользователей этого класса требуются всего лишь азы линейной алгебры. Лучшим вариантом будет реализация различных функциональных областей в виде функций, не являющихся членами, которые работают с минимальным типом Matrix
. Тогда эти функциональные области могут быть протестированы и использованы отдельно только теми пользователями, кто в них нуждается (см. рекомендацию 44).
• Монолитные классы снижают инкапсуляцию. Если класс имеет много функций-членов, которые не обязаны быть членами, но тем не менее являются таковыми (таким образом обеспечивается излишняя видимость закрытой реализации), то закрытые члены-данные класса становятся почти столь же плохими с точки зрения дизайна, как и открытые переменные.
• Монолитные классы обычно являются результатом попыток предсказать и предоставить "полное" решение некоторой проблемы; на практике же такие действия почти никогда не приводят к успешному результату.
• Монолитные классы сложнее сделать корректными и безопасными в связи с тем, что при их разработке зачастую нарушается принцип "Один объект — одна задача" (см. рекомендации 5 и 44).
34. Предпочитайте композицию наследованию
Избегайте "налога на наследство": наследование — вторая по силе после отношения дружбы взаимосвязь, которую можно выразить в С++. Сильные связи нежелательны, и их следует избегать везде, где только можно. Таким образом, следует предпочитать композицию наследованию, кроме случаев, когда вы точно знаете, что делаете и какие преимущества дает наследование в вашем проекте.
Наследованием часто злоупотребляют даже опытные разработчики. Главное правило в разработке программного обеспечения — снижение связности. Если взаимоотношение можно выразить несколькими способами, используйте самую слабую из возможных взаимосвязей.