Fallible construction (even with a way to report failure) is annoying if
only because the object's destructor has to account for the possible
invalid states. I much prefer having a static creation method that will
only instantiate the object in case of success, and mark the constructor
protected. Something like:

static
already_AddRefed<Foo> Foo::Create(SomeParams...) {
    // logic that may fail...
    if (failed) {
         return nullptr;
    }
    return MakeAndAddRef<Foo>(stuffThatDidNotFail);
}

Between having ways to communicate that an object is constructed in an
invalid state, and never having invalid states, I much prefer the
latter.

Cheers,

Nical

On Thu, Apr 21, 2016, at 07:05 AM, Gerald Squelart wrote:
> On Thursday, April 21, 2016 at 11:15:10 AM UTC+10, Nicholas Nethercote
> wrote:
> > Hi,
> > 
> > C++ constructors can't be made fallible without using exceptions. As a 
> > result,
> > for many classes we have a constructor and a fallible Init() method which 
> > must
> > be called immediately after construction.
> > 
> > Except... there is one way to make constructors fallible: use an |nsresult&
> > aRv| outparam to communicate possible failure. I propose that we start doing
> > this.
> > 
> > Here's an example showing stack allocation and heap allocation. Currently, 
> > we
> > do this (boolean return type):
> > 
> >   T ts();
> >   if (!ts.Init()) {
> >     return NS_ERROR_FAILURE;
> >   }
> >   T* th = new T();
> >   if (!th.Init()) {
> >     delete th;
> >     return NS_ERROR_FAILURE;
> >   }
> > 
> > or this (nsresult return type):
> > 
> >   T ts();
> >   nsresult rv = ts.Init();
> >   if (NS_FAILED(rv)) {
> >     return rv;
> >   }
> >   T* th = new T();
> >   rv = th.Init();
> >   if (NS_FAILED(rv)) {
> >     delete th;
> >     return rv;
> >   }
> > 
> > (In all the examples you could use a smart pointer to avoid the explicit
> > |delete|. This doesn't affect my argument in any way.)
> > 
> > Instead, we would do this:
> > 
> >   nsresult rv;
> >   T ts(rv);
> >   if (NS_FAILED(rv)) {
> >     return rv;
> >   }
> >   T* th = new T(rv);
> >   if (NS_FAILED(rv)) {
> >     delete th;
> >     return rv;
> >   }
> > 
> > For constructors with additional argument, I propose that the |nsresult&|
> > argument go last.
> > 
> > Using a bool outparam would be possible some of the time, but I suggest 
> > always
> > using nsresult for consistency, esp. given that using bool here would be no
> > more concise.
> > 
> > SpiderMonkey is different because (a) its |operator new| is fallible and 
> > (b) it
> > doesn't use nsresult. So for heap-allocated objects we *would* use bool, 
> > going
> > from this:
> > 
> >   T* th = new T();
> >   if (!th) {
> >     return false;
> >   }
> >   if (!th.Init()) {
> >     delete th;
> >     return false;
> >   }
> > 
> > to this:
> > 
> >   bool ok;
> >   T* th = new T(ok);
> >   if (!th || !ok) {
> >     delete th;
> >     return false;
> >   }
> > 
> > These examples don't show inheritance, but this proposal works out
> > straightforwardly in that case.
> > 
> > The advantages of this proposal are as follows.
> > 
> > - Construction is atomic. It's not artificially split into two, and there's 
> > no
> >   creation of half-initialized objects. This tends to make the code nicer
> >   overall.
> > 
> > - Constructors are special because they have initializer lists -- there are
> >   things you can do in initializer lists that you cannot do in normal
> >   functions. In particular, using an Init() function prevents you from using
> >   references and |const| for some members. This is bad because references 
> > and
> >   |const| are good things that can make code more reliable.
> > 
> > - There are fewer things to forget at call sites. With our current approach 
> > you
> >   can forget (a) to call Init(), and (b) to check the result of
> > Init(). With this
> >   proposal you can only forget to check |rv|.
> > 
> > The only disadvantage I can see is that it looks a bit strange at first. 
> > But if
> > we started using it that objection would quickly go away.
> > 
> > I have some example patches that show what this code pattern looks like in
> > practice. See bug 1265626 parts 1 and 4, and bug 1265965 part 1.
> > 
> > Thoughts?
> > 
> > Nick
> 
> (busy right now, please excuse terseness & typos!)
> 
> Big thumbs up for trying to remove split construction&inits.
> 
> My main beef with this proposal is the use of out-params, which require
> (usually uninitialized) declaration of the out-param.
> But I see that it may indeed be the best solution here, so ... fiiiine!
> 
> However, since lots of Mozilla objects are unique-ptr'd or ref-counted, I
> would argue that we could easily fold the construction checks with the
> nullptr-checks that we all know&love!
> 
> So in addition to your proposal, I would like to see a small library of
> tools that will build on top of your new style, and make it easier &
> cleaner & consistent to use in those cases.
> 
> E.g.:
>   template <typename T, typename ...Args>
>   T* newWithCheck(Args&&... aArgs)
>   {
>     nsresult rv;
>     T* p = new T(std::forward<Args>(aArgs)..., &rv);
>     if (p) { // <- this test could be removed for non-fallible new.
>       if (NS_SUCCEEDED(rv)) {
>         return p;
>       }
>       delete p; // "Failed" construction -> Just delete the thing.
>     }
>     return nullptr;
>   }
> 
> Then instead of:
>   nsresult rv; // Yuck!
>   RefPtr<Foo> foo = new Foo(a, b, &rv); // Hiss!
>   if (NS_SUCCEEDED(rv)) { ... // Can we really trust foo here? Boo!
> 
> We could do things like:
>   RefPtr<Foo> foo = newWithCheck<Foo>(a, b); // Beauty!
>   if (foo) { ... // Construction-check & nullptr-check in one test!
> 
> Am I showing some bias? :-)
> 
> We could have similar 'new' functions that would populate a Maybe<T>
> instead, or that would expose the nsresult where useful (e.g. through
> Pair<nsresult, RefPtr<T>>), etc.
> 
> 
> And of course, there's still the issue of missing checks.
> 
> Your style probably helps, as the compiler and/or static analyzers should
> be able to see that you're defining 'rv', writing to it, but not reading
> it. If that's true, my proposal might mess with that. :-(
> 
> So to combat that, could we create some new smart pointer type, that
> asserts that there is a nullptr check (through 'explicit operator
> bool()') before trying to dereference it? More thinking required...
> _______________________________________________
> dev-platform mailing list
> dev-platform@lists.mozilla.org
> https://lists.mozilla.org/listinfo/dev-platform
_______________________________________________
dev-platform mailing list
dev-platform@lists.mozilla.org
https://lists.mozilla.org/listinfo/dev-platform

Reply via email to