I like to claim that operator new, or object construction is a Factory pattern. This is almost guaranteed to cause a lot of noise...
But let me elaborate:
// Returns instance of foo, or null if creation failedFoo * factory(Parameters ...);
Clean, simple, doesn't require exceptions, is perfectly safe - object is created fully valid - or not at all.
Equivalent using RAII (in any language):
class Foo { Foo(Parameters) { // throw on error }}
Here, Foo is either created fully valid, or not at all. The RAII constraints are met - object acquisition also implies initialization. Destruction is not needed for sake of correctness (but not necessarily system constraints or optimization) in post-C++ languages, in C++ it is handled properly as far as RAII goes.
What if exceptions are not available, or scope of object is managed elsewhere:
// initializes t, returns true on success, false on failurebool factory(Target & t);
This works with auto-allocated objects, or instances. It is also transferrable to procedural languages. Exceptions can be used as well.
While it is now possible to argue semantics of exceptions, "safety", etc... none of those are really relevant for the actual problem at hand. Object construction needs to satisfy the following two goals:
- Act of initialization has pre-condition of undefined state, and post-condition of fully initialized and valid instance
- User must never be left with instance where post-condition holds
This means there are the following scenarios:
try { Foo foo(); // or Foo * foo = new Foo(); // of Foo * foo = create();} catch (InitializationException e) {}
Alternatively:
Foo * foo = (Foo *) malloc(...);if (!foo) // errorFoo * foo = create();if (!foo) // errorFoo foo;if (!create(foo)) // errorFoo foo;if (!create(&foo)) // error
This covers effectively all possible cases, highlighting different tradeoffs. C++ new is not included since non-exception versions would require non-throwing new, which cannot be specified explicitly in code. This means that new would be covered under create() version, ensuring the creation post-condition.
Initialize() member functions are also not included, since they are just a different version of create(foo), where foo is passed explicitly. Even more - initialize member functions do not satisfy the pre-condition. They might require vtable or even something more complex which we cannot guarantee. Pre-condition states that object, even if present is some form, is in undefined state. This requires breaking initialization into two parts, such as new + initialize which is very undesirable.
Whether return values are checked or not is also not a big issue. Lack of checking can be automatically tested as part of compilation process. Such interface will be non-C++ anyway, so the syntax is simpler to parse. But it can be fully automated and cause the build to fail.
But as far as design goes - they are all exactly the same. A Factory pattern. Put in a bunch of keys, get back either an instance, or nothing. Everything else is just a trade-off depending on run-time environment, compiler, APIs, performance, ....