-1

When writing exception-safe code, should all private member functions guarantee at least basic exception safety? What would be best/good practice in this situation? Alternatives?

For example, say I have a class Foo with public function member DoSomething, which calls private function member DoSomeOfIt. DoSomeOfIt may fail due to some functions that I cannot influence, and when it does it may leave the Foo object in a partially modified state which violates Foo's invariants. So I could wrap DoSomeOfIt in a try-catch block and call another private function member UndoThat in the catch block to undo what DoSomeOfIt did. Individually, DoSomeOfIt and UndoThat may not be exception safe, but DoSomething is. Would that be exception safe code? (In this case with strong guarantee)

class Foo {
public:
  // I provide strong exception safety guarantee
  void DoSomething() {
    try {
      DoSomeOfIt();
    } catch (const std::exception& e) {
      UndoThat();
      throw;
    }
  }
private:
  // I provide no exception safety guarantee.
  DoSomeOfIt() {
    // may throw and violate class invariants.
  }

  // I provide no exception safety guarantee.
  UndoThat() {
    // undoes everything most recent call of
    // DoSomeOfIt did.
  }
};

Of course I could simply include the code of DoSomeOfIt and UndoThat in DoSomething, but that could lead to code bloating, and a long function body, whereas breaking up the functions modularizes the tasks and may make the code more readable(?)

DISCLAIMER: I understand this may be opinion-based. I am not sure if that makes this a bad post, but I'd appreciate any opinions, or experiences where this has lead to issues, or is common practice etc.

Jasper Braun
  • 109
  • 8
  • 1
    Opinion based - it's a detail of the implementation; the users `Foo` of only care about its public (and protected) interface. The private implementation can do what it likes, in whatever way it wants to so longs the public contract (including exception guarantees) is fulfilled. – Richard Critten Jul 20 '20 at 18:19
  • 2
    Anytime I write `catch` in C++, I wonder to myself if RAII would be better. Usually, (not always), I find RAII to be better. Since you imply that `UndoThat()` can throw exceptions, this is probably a case where RAII is not the best answer, but I thought I'd comment for subsequent readers. `void DoSomething() {StateSave saveState; DoSomeOfIt(); saveState.setSucceeded();}` – Mooing Duck Jul 20 '20 at 18:28

1 Answers1

1

This is my opinion.

If there is a possibility that DoSomeOfIt will be used by more than one function, it'll be better to have the exception handling code reside in that function itself -- you don't want to have the exception handling code duplicated across multiple functions. If that is not a possibility, your posted code is fine.

Having said that, if you move the exception handling code to DoSomething, you don't lose anything. In fact, the function becomes better. If it is used in the future by another function, the exception handling is already taken care of. Looked at from that point of view, it will be better to move the exception handling code to DoSomething.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • I can see the benefit of this. However, `UndoThat` has the same issue. But I guess by this logic, `UndoThat` would only be called by `DoSomeOfIt`, so it wouldn't be a problem. – Jasper Braun Jul 20 '20 at 18:27
  • 2
    I would personally go stricter and say that `DoSomeOfIt` should _always_ handle the exception handling. There's no telling how it may be used in the future by the next maintainer, and it's an easier mental model to assume that all class invariants are upheld all the time – Human-Compiler Jul 20 '20 at 18:28
  • @Human-Compiler Of course the whole thing is opinion based, but I'd say this is a tradeoff between the simpler mental model which upholds all class invariants all the time, and possible code-bloat. `DoSomeOfIt` may be a complex function calling other functions and `DoSomething` may be doing a lot more than just `DoSomeOfIt`, perhaps with similar problems. Eventually, I would wonder if the tradeoff would lean towards modularization. – Jasper Braun Jul 20 '20 at 18:31