Я пишу класс C ++. Некоторые из его полей являются контейнерами STL, некоторые нет. При написании методов я начал задаваться вопросом, как мне обращаться с неверными значениями, передаваемыми в методы? Например, некоторые методы являются более или менее обертками для методов контейнера STL. Многие методы STL просто имеют «неопределенное поведение», когда передаются недопустимые итераторы. Я думаю, что это так, потому что это позволяет коду STL игнорировать эти случаи и, следовательно, быть быстрее.
Но что делать с кодом более высокого уровня? Я выбрасываю исключения, когда возникает непредвиденная ошибка, например, ошибка, допущенная разработчиком. Но в этом случае значение параметра зависит от пользователя интерфейса, а не от разработчика. Я мог игнорировать недопустимые параметры и недопустимые итераторы и т. Д. И «передавать» проблему функциям более низкого уровня, которые затем имели бы неопределенное поведение, но я мог также вызвать исключение или, по крайней мере, найти способ сообщить об ошибке.
Что было бы лучше всего сделать?
Пример: у меня есть класс, представляющий узел дерева, и у него есть метод add_child (), который принимает параметр std :: shared_ptr. Должен ли я проверить значение или позволить пользователю убедиться, что nullptr не передан? Или для недействительного итератора передать его в методы STL или сообщить об ошибке? Если я должен сообщить — исключения — правильное решение?
Я думаю, что универсальный ответ не может быть дан. Слишком много вещей влияют на решение, которое вы не упомянули: во-первых, вы разрабатываете библиотеку или приложение?
В первом случае следует отдавать предпочтение гибкости, и лучшим выбором обычно является разработка интерфейса таким образом, чтобы решение оставалось за клиентом. Поскольку вы не знаете, какие клиенты будут использовать ваш класс и какие требования они будут предъявлять, лучше всего сделать как можно меньше предположений. Если ваши клиенты предъявляют жесткие требования к производительности, они, например, не будут платить штраф за производительность при проверке дополнительных границ. Это, в основном, причина того, почему связанные проверки не выполняются STL.
Во втором случае это зависит от требований к производительности вашего приложения и исключительных гарантий безопасности, которые должен предоставить ваш класс. Какие операции должны быть без броска? Вы разрешаете или планируете использовать свой класс в контейнере STL? Будет ли он использоваться в наиболее важной части вашего приложения, чтобы небольшой выигрыш в производительности алгоритмов этого класса повлиял на общее время выполнения вашего приложения?
Нет такой вещи, как «универсально лучший дизайн». Инженерия — это поиск правильного компромисса в зависимости от конкретной ситуации, и разработка программного обеспечения не является исключением.
В таких случаях я бы использовал утверждения как в:
assert(i < vec.size());
return vec[i];
Таким образом, если программа скомпилирована с флагом NDEBUG, вы получите максимальную производительность и не будете проверять. Во время разработки вы можете получать ошибки во время выполнения и отлаживать свой код. Макрос assert доступен с #include < cassert>.