Создание типов с ограниченными свойствами - PullRequest
1 голос
/ 17 мая 2019

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

Например, есть классы vector2 и vector2_normalized, в которых операторы vector2_normalized, которые могут изменять длину вектора (+, -, scalar * и /, ..), возвращают экземпляр vector2, а другие возвращают экземпляр vector2_normalized. Затем используйте неявное преобразование для автоматического переключения между ними. Таким образом, векторы, которые должны быть нормализованы, могут использовать этот тип, и ошибки нормализации устраняются.

Ответы [ 4 ]

1 голос
/ 17 мая 2019

Да

Эти "ограничения", о которых вы говорите, называются инвариантами класса , а класс - это способ создания объекта домена, ограничивающий его допустимость. Это одна из основных причин использования классов.

Арно Лепсик недавно выступил на CppCon 2018 с прекрасной лекцией на эту тему, которая называется «Предотвращение бедствий со строго типизированным C ++»

Джон Лакос также выступил с превосходным докладом об этом на CppCon 2015 под названием «Семантика значений. Это не о синтаксисе»

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

Один замечательный пример этого - Boost.Units .

Если вам когда-либо приходилось иметь дело с программированием научных приложений, то вы знаете, что работа с юнитами - это боль.

Как вы обеспечиваете правильность операций между вашими данными? Вы не хотите прибавлять метры к футам, вот как вы разбиваете ракеты. Когда ваши значения становятся строго типизированными вашими единицами, такая операция становится невозможной во время компиляции .

0 голосов
/ 17 мая 2019

Да - повторяя все, что AndyG говорит об инвариантах классов и объектах домена - с одним предупреждением.

Разрешение неявного преобразования между типами создает риск, что ваш код накапливает тихие преобразования. Часто лучше вместо этого использовать явную функцию преобразования. Например, с

vec2 fun_a();
vec2 fun_b(norm_vec2 const&);
vec2 fun_c(norm_vec2 const&);

Вы можете написать

norm_vec2 v = fun_c(fun_b(fun_a()));

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

0 голосов
/ 17 мая 2019

Ну, есть std::string и std::filesystem::path.Но нет ни std::uppercase_string, ни std::russian_string, ни каких-либо других ...

Посмотрите, имеют ли эти ограничения огромное значение для интерфейса вашего конкретного класса.Если ограничения допускают какую-то совершенно новую операцию, немыслимую в общем классе, то она, безусловно, заслуживает выделенного класса с расширенным общедоступным интерфейсом.

И я бы, конечно, сказал громкое НЕТ, чтобы замаскировать значимую операцию как неявное преобразование,Тем более, если конверсия необратима.Такое преобразование может показаться заманчивым, и оно вполне может устранить некоторые текущие ошибки, вызванные пренебрежением, но также вероятно, что оно может привести к появлению совершенно новых ошибок, если нежелательное преобразование появилось без вашего ведома.

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

0 голосов
/ 17 мая 2019

Да, именно поэтому у нас есть общая концепция private членов. Он существует для устранения всех ошибок, которые могут возникнуть из-за недопустимых значений элементов. Он делает это, вместо самого члена, выставляя интерфейс (перегруженные операторы и функции), чтобы вы могли изменять эти элементы контролируемым образом, где внутренние значения корректируются соответствующим образом.

...