Не используйте либо, если вы можете избежать этого. Используйте фабричную функцию, возвращающую тип опции, кортеж или выделенный тип суммы. Другими словами, представляйте потенциально дефолтное значение с типом, отличным от гарантированного не подлежащего дефолту значения.
Во-первых, давайте перечислим desiderata, а затем подумаем, как мы можем выразить это на нескольких языках (C ++, OCaml, Python)
- поймать нежелательное использование объекта по умолчанию во время компиляции.
- сделайте очевидным, является ли данное значение потенциально дефолтным или нет при чтении кода.
- выберите наше разумное значение по умолчанию, если применимо, один раз для каждого типа . Определенно не выбирайте потенциально разные значения по умолчанию на каждом сайте вызовов .
- упростите инструмент статического анализа или человека, который
grep
может выследить возможные ошибки.
- Для некоторых приложений программа должна продолжаться в обычном режиме, если неожиданно задано значение по умолчанию. Для других приложений программа должна немедленно остановиться, если задано значение по умолчанию, в идеале информативным способом.
Я думаю, что напряжение между шаблоном нулевого объекта и нулевыми указателями исходит из (5). Если мы сможем обнаружить ошибки достаточно рано, то (5) станет спорным.
Давайте рассмотрим этот язык по языкам:
C ++
На мой взгляд, класс C ++ обычно должен конструироваться по умолчанию, поскольку он облегчает взаимодействие с библиотеками и облегчает использование класса в контейнерах. Это также упрощает наследование, поскольку вам не нужно думать о том, какой конструктор суперкласса вызывать.
Однако это означает, что вы не можете точно знать, находится ли значение типа MyClass
в «состоянии по умолчанию» или нет. Помимо того, что вы вставляете bool nonempty
или аналогичное поле для отображения поверхностных настроек по умолчанию во время выполнения, лучшее, что вы можете сделать, - это создать новые экземпляры MyClass
таким образом, чтобы заставить пользователя проверить его .
Я бы порекомендовал использовать фабричную функцию, которая возвращает std::optional<MyClass>
, std::pair<bool, MyClass>
или, std::unique_ptr<MyClass>
если возможно, ссылку на r-значение .
Если вы хотите, чтобы ваша фабричная функция возвращала какое-то состояние "заполнителя", отличное от созданного по умолчанию MyClass
, используйте a std::pair
и обязательно задокументируйте, что ваша функция делает это.
Если фабричная функция имеет уникальное имя, ее легко grep
найти и искать неряшливость. Тем не менее, это трудно grep
для случаев, когда программист должен был использовать заводскую функцию, но не использовал ее .
OCaml
Если вы используете такой язык, как OCaml, то вы можете просто использовать option
тип (в стандартной библиотеке OCaml) или either
тип (не в стандартной библиотеке, но легко развернуть свой собственный). Или defaultable
тип (я придумываю термин defaultable
).
type 'a option =
| None
| Some of 'a
type ('a, 'b) either =
| Left of 'a
| Right of 'b
type 'a defaultable =
| Default of 'a
| Real of 'a
A, defaultable
как показано выше, лучше, чем пара, потому что пользователь должен сопоставить шаблон для извлечения 'a
и не может просто игнорировать первый элемент пары.
Эквивалент defaultable
типа, показанного выше, можно использовать в C ++, используя a std::variant
с двумя экземплярами одного типа, но части std::variant
API непригодны, если оба типа одинаковы. Также это странное использование, std::variant
так как его конструкторы типов не называются.
питон
Вы все равно не получаете никакой проверки во время компиляции для Python. Но, будучи динамически типизированным, обычно нет обстоятельств, когда вам нужен экземпляр заполнителя какого-либо типа, чтобы успокоить компилятор.
Я бы порекомендовал просто вызвать исключение, когда вам придется создать экземпляр по умолчанию.
Если это неприемлемо, я бы порекомендовал создать объект, DefaultedMyClass
который наследуется, MyClass
и вернуть его из функции фабрики. Это дает вам гибкость в плане «заглушения» функциональности в дефолтных экземплярах, если вам нужно.