Не используйте либо, если вы можете избежать этого. Используйте фабричную функцию, возвращающую тип опции, кортеж или выделенный тип суммы. Другими словами, представляйте потенциально дефолтное значение с типом, отличным от гарантированного не подлежащего дефолту значения.
Во-первых, давайте перечислим 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::variantAPI непригодны, если оба типа одинаковы. Также это странное использование, std::variantтак как его конструкторы типов не называются.
питон
Вы все равно не получаете никакой проверки во время компиляции для Python. Но, будучи динамически типизированным, обычно нет обстоятельств, когда вам нужен экземпляр заполнителя какого-либо типа, чтобы успокоить компилятор.
Я бы порекомендовал просто вызвать исключение, когда вам придется создать экземпляр по умолчанию.
Если это неприемлемо, я бы порекомендовал создать объект, DefaultedMyClassкоторый наследуется, MyClassи вернуть его из функции фабрики. Это дает вам гибкость в плане «заглушения» функциональности в дефолтных экземплярах, если вам нужно.