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

Reply via email to