The pattern seems reasonable enough.

DXR says we have 2579 "init" or "Init" functions, and 9801 callers of such
functions. I used:

function:init ext:cpp ext:h
callers:init ext:cpp ext:h

Do you propose we make an effort to fix up existing code, or just introduce
this as the preferred pattern in new code?


On Wed, Apr 20, 2016 at 6:07 PM, Nicholas Nethercote <n.netherc...@gmail.com
> 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
> _______________________________________________
> 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