0

I have the below code snippet which is to demonstrate use-case of function-try-block for constructor. Because the constructor of Controller throws no object of it gets created successfully, i.e. the client_ member variable should no longer exist too when exception is thrown, is that correct? If so then is that safe to call deinit() which is to clean-up the client_? If not safe, say if client_ was a raw pointer which then how would I catch the exception and do clean-up stuff for it?

class Client {
 public:
  Client() { std::cout << "Client()" << std::endl; }
  ~Client() { std::cout << "~Client()" << std::endl; }
  void Start() { throw "Start() failed with exception"; }
};

class Controller {
 private:
  std::shared_ptr<Client> client_;

 public:
  Controller() try {
    client_ = std::make_shared<Client>();
    client_->Start();
  } catch (...) {
    deinit();
  }

  ~Controller() {
    deinit();
  }

 private:
  void deinit() {
    if (client_) {
      client_.reset();
    }
  }
};

duong_dajgja
  • 4,196
  • 1
  • 38
  • 65
  • See [except.ctor#3](http://eel.is/c++draft/except.ctor#3) : _"If the initialization of an object other than by delegating constructor is terminated by an exception, the destructor is invoked for each of the object's subobjects that were known to be initialized by the object's initialization and whose initialization has completed"_ – paddy Mar 31 '23 at 02:57
  • In my code above, is the destructor for each of the object's subobjects invoked even before the control reaches the `catch(...)` block? If that is the case then checking `if (client_)` in `deinit()` cannot be safe as the `client_` object has been destroyed right? – duong_dajgja Mar 31 '23 at 03:00
  • 1
    1. Yes, and you can see this in action by outputting a message at the beginning of the `catch`. 2. Yeah, that makes sense. [Here](https://godbolt.org/z/Kx99Mzoaf) is a more explicit example of the object being destroyed. Imagine that was your shared_ptr not caring about a dangling pointer when you destroy it. Or indeed, if you were using some allocator that writes a bit pattern into memory after destroying an object, for debugging stuff like this. – paddy Mar 31 '23 at 03:10

1 Answers1

1

the client_ member variable should no longer exist too when exception is thrown, is that correct?

No this is incorrect. The exception gets thrown after the client_ member of the new class instance gets constructed.

A class's constructor gets called only after all members of the new class instance get constructed; and the constructor's job is to do whatever it means to construct the class itself, after all the class members get constructed.

Once you're in the constructor, you're guaranteed that all the members of the new class instance are fully constructed. You can take that to the bank.

is that safe to call deinit()

Well, it's "safe" since it's defined behavior. But, in this case, it is completely unnecessary. If an exception gets thrown in the constructor, all constructed members of the new class instance get automatically destroyed, in reverse construction order. client_ will get destroyed normally, as if it would if the class fully constructed and then destroyed at some point later.

if client_ was a raw pointer which then how would I catch the exception and do clean-up stuff for it?

Pretty much the way the shown code does it. But it is not a raw pointer, but a shared_ptr, which handles this.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • As per this https://stackoverflow.com/questions/10212842/what-destructors-are-run-when-the-constructor-throws-an-exception and this http://www.gotw.ca/gotw/066.htm, the `client_` will be destructed at very end of the `try` block, even before reaching to the `catch` block, and this means that the access to `client_` (comparing to null) is UB, IMO. – duong_dajgja Mar 31 '23 at 02:55
  • From this en.cppreference.com/w/cpp/language/function-try-block: "Before any catch clauses of a function-try-block on a constructor are entered, all fully-constructed members and bases have already been destroyed.", this means that `client_` object would have been destroyed even before control flow reaches `catch(...)` – duong_dajgja Mar 31 '23 at 05:32
  • Even though you have the constructor body in a `try`/`catch` block, by the time the constructor body is entered all class members are already constructed. The presence of the `try`/`catch` block does not change this. So, if the constructor body throws an exception the constructed class member will get destroyed as part of throwing the exception. In fact, this is actually undefined behavior, since calling `deinit` from the `catch` handler will access a destroyed class member. – Sam Varshavchik Mar 31 '23 at 12:01
  • That's true. I am wondering why you haven't updated your answer yet, as you mentioned that "it's "safe" since it's defined behavior"... – duong_dajgja Apr 01 '23 at 06:13