
On Wed, Jan 31, 2018 at 10:30 PM, Gavin Lambert via Boost < boost@lists.boost.org> wrote:
On 1/02/2018 19:09, Emil Dotchevski wrote:
The factory method technique also allows somewhat restoring a stronger
invariant -- only the constructor and destructor need to cope with empty or otherwise uninitialised instances; other methods can be reasonably assured that no such instances escaped the factory method.
This is true only if you use exceptions to report errors from the factory. Otherwise it is most definitely NOT reasonable for member functions to assume that the object has been initialized, because nothing guarantees that an error reported by the factory wasn't ignored.
The factory itself provides that guarantee. One such pseudo-code implementation might be:
static std::unique_ptr<A> A::create(args...) noexcept { // the constructor is itself noexcept and cannot fail std::unique_ptr<A> a(new (std::nothrow) A(arg1)); if (!a) return nullptr;
if (!a->private_init_stuff(arg2, arg3, ...)) return nullptr;
return a; }
This does not protect the user at all: std::unique_ptr<A> a=A::create(); a->foo(); //undefined behavior Compare to: static std::unique_ptr<A> A::create(args...) { std::unique_ptr<A> a(new A(arg1)); //new throws on error, A::A() throws on error return a; } And then: std::unique_ptr<A> a=A::create(); a->foo(); //Okay, a is guaranteed to be valid. Once again, you'll notice that if you use exceptions, the compiler "writes" the correct error checks for you, and the user is automatically protected. With Boost.Outcome, it can be implemented exactly as above (including not
letting an invalid instance escape), but *also* indicate a failure reason to the caller, through a simple change to the return type.
This is not an advantage over using exceptions, and besides it is only true in the simplest of cases. The problem is that Outcome requires users to fit any and all possible failures into a single error type, which is not always possible. In general, you can neither enumerate nor reason about all failures which may occur. Notably, the one Outcome feature that can deal with this problem is its ability to transport std::exception_ptr. :)
Are exceptions cleaner and shorter? Of course. But this still works, and some people will prefer it due to perceived latency issues with exceptions, or just personal issues with exceptions in general, or with "hidden code paths" expressly due to the code being shorter. (Whether those issues are real or not, I cannot say.)
Well, I can. :) Emil