Don't use either if you can avoid it. Use a factory function returning an option type, a tuple, or a dedicated sum type. In other words, represent a potentially-defaulted value with a different type from a guaranteed-not-to-be-defaulted value.
First, let's list the desiderata then think about how we can express this in a few languages (C++, OCaml, Python)
- catch unwanted use of a default object at compile time.
- make it obvious whether a given value is potentially-defaulted or not while reading the code.
- choose our sensible default value, if applicable, once per type. Definitely don't pick a potentially different default at every call site.
- make it easy for static analysis tools or a human with
grep to hunt for potential mistakes.
- For some applications, the program should continue normally if unexpectedly given a default value. For other applications, the program should halt immediately if given a default value, ideally in an informative way.
I think the tension between the Null Object Pattern and the null pointers comes from (5). If we can detect errors early enough, though, (5) becomes moot.
Let's consider this language by language:
In my opinion, a C++ class should generally be default-constructible since it eases interactions with libraries and makes it easier to use the class in containers. It also simplifies inheritance since you don't need to think about which superclass constructor to call.
However, this means that you can't know for sure whether a value of type
MyClass is in the "default state" or not. Besides putting in a
bool nonempty or similar field to surface default-ness at runtime, the best you can do is produce new instances of
MyClass in a way that compels the user to check it.
I'd recommend using a factory function that returns a
std::pair<bool, MyClass> or an r-value reference to a
std::unique_ptr<MyClass> if possible.
If you want your factory function to return some kind of "placeholder" state that's distinct from a default-constructed
MyClass, use a
std::pair and be sure to document that your function does this.
If the factory function has a unique name, it's easy to
grep for the name and look for sloppiness. However, it is hard to
grep for cases for where the programmer should have used the factory function but didn't.
If you are using a language like OCaml, then you can just use an
option type (in the OCaml standard library) or an
either type (not in the standard library, but easy to roll your own). Or a
defaultable type (I'm making up the term
type 'a option =
| Some of 'a
type ('a, 'b) either =
| Left of 'a
| Right of 'b
type 'a defaultable =
| Default of 'a
| Real of 'a
defaultable as shown above is better than a pair because the user must pattern match to extract the
'a and can't simply ignore the first element of the pair.
The equivalent of the
defaultable type as shown above can be used in C++ using a
std::variant with two instances of the same type, but parts of the
std::variant API are unusable if both types are the same. Also it's a weird use of
std::variant since its type constructors are unnamed.
You don't get any compile-time checking anyway for Python. But, being dynamically typed, there generally aren't circumstances where you need a placeholder instance of some type to placate the compiler.
I would recommend just throwing an exception when you would be forced to create a default instance.
If that's not acceptable, I would recommend creating a
DefaultedMyClass that inherits from
MyClass and returning that from your factory function. That buys you flexibility in terms of "stubbing out" functionality in defaulted instances if you need to.