Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

>Which is already what the standard does to closures: you are basically forced to use smart pointers and capture by value if you are returning a closure from a function.

Well, you have to make sure somehow that the thing a pointer is pointing to will still be there when the closure gets executed, sure. But the change your asking for would have a much wider impact. Anywhere you have a dynamically allocated pointer, or really anything that needs destruction whatsoever, without having an already-associated destructor would become unsafe for exceptions. Which is commonly the case in constructors. It's basically this pattern (which is extremely common) that would become prohibited:

  class foo
  {
	foo() {
		do_X();
		try {
			may_throw_exception();
		} catch(…) {
			undo_X();
			throw;
		}
	}
	~foo() { undo_X(); }
  };
What you would need to do is exclude exceptions from passing through any constructor that has an associated destructor, because the destructor wouldn't be called if the constructor throws and the catch block couldn't safely destroy the resources before the stack is unwound.

>So have the catch set a flag, save a copy of the exception, and then after the catch block the flag is checked; if the flag is set, free the memory and throw the copied exception.

Obviously it can be worked around, but can you see how quickly it becomes a headache? And now you're adding code and complexity to operator new, which is a good candidate for the most frequently called function in any given program.

>Or just give programmers a way to explicitly unwind the stack at any point in a catch block.

In other words, the existing functionality is good and necessary for some circumstances, but you want something different in addition to it.

It seems like you're looking for something like this:

  void foo::bar()
  {
	connect_network_drives();
	try {
		do_some_stuff();
	} catch(file_write_exception) {
		check_and_reconnect_network_drives();
		resume;
	}  catch(fatal_file_write_exception) {
		// epic fail, maybe network is dead
		// handle serious error, maybe terminate etc.
	}
  }
  void do_some_stuff()
  {
	// …
	file.write(stuff);
	if(file.write_failed()) {
		throw file_write_exception();
		on resume {
			file.write_stuff(stuff); // try again
			// no resume this time if error not fixed
			if(file.write_failed())
				throw fatal_file_write_exception(); 
		}
	}
  }
But if that's what you want, why do you need special language support, instead of just doing something like this?

  // (this class could be a template for multiple different kinds of errors)
  class file_write_error
  {
  private:
	static thread_local std::vector< std::function<void()> > handlers;
  public:
	file_write_error(std::function<void()>& handler) {
		handlers.push_back(handler);
	}
	~file_write_error() {
		handlers.pop_back();
	}
	static void occurred() {
		// execute most recently registered handler
		if(handlers.size() > 0)
			handlers.back()();
	}
  };
  // (and then poison operator new for file_write_error
  // so it can only be allocated on the stack
  // and destructors run in reverse construction order)
  void foo::bar()
  {
	connect_network_drives();
	try {
		file_write_error handler([this]() {
			check_and_reconnect_network_drives();
		});
		do_some_stuff();
	} catch(fatal_file_write_exception) {
		// epic fail, maybe network is dead
		// handle serious error, maybe terminate etc.
	}
  }
  void do_some_stuff()
  {
	// …
	file.write(stuff);
	if(file.write_failed()) {
		file_write_error::occurred(); // handle error
		file.write_stuff(stuff); // try again
		if(file.write_failed()) 
			throw fatal_file_write_exception();
	}
  }
It seems like you're just looking for an error callback that gets called to try to fix a problem before throwing a fatal stack-unwinding exception is necessary. And it's not a bad idea, maybe more people should do that. But doesn't the language already provides what is necessary to accomplish that? Are we just arguing about syntax?

You could easily use that sort of error handler in a destructor as an alternative to exceptions as it is now. There is nothing the error handler can't do that a catch block could before stack unwinding. And if you call such a thing and it fails, the two alternatives of either ignoring the error or terminating the program are all you really have left anyway, because if there was anything else to do then you could have either done it in the destructor or in the error handler. (I can imagine that an error handler may benefit from being able to call the next one up in the hierarchy if any, analogous to 'throw' from a catch block, but that could be accomplished with minimal changes to the above.)



"In other words, the existing functionality is good and necessary for some circumstances, but you want something different in addition to it."

The problem with the existing approach is that the safety of throwing an exception from a destructor depends on the context in which the destructor is invoked, and there is no workaround for it. What I am proposing would ensure that the safety of throwing an exception would not be dependent on why the function throwing the exception was called; edge cases where this might be unsafe could either be worked around (perhaps in a headache-inducing way, but nothing close the headaches associated with destructors having no way to signal errors), or a language feature could be added to solve those problems.

"It seems like you're just looking for an error callback that gets called to try to fix a problem before throwing a fatal stack-unwinding exception is necessary. And it's not a bad idea, maybe more people should do that. But doesn't the language already provides what is necessary to accomplish that? Are we just arguing about syntax?"

Well, if we are arguing about programming languages, what is wrong with arguing about syntax? The language does provide enough features to manually implement restarts -- but if that is your argument, why do you even bother with C++? C gives you everything you need to implement any C++ feature; assembly language gives you all you need to implement any C feature. We use high-level languages because our productivity is greatly enhanced by having things automated.

Take exception handling itself as an example. We do not actually need the compiler to set it up for us -- we already have setjmp/longjmp, which are enough to manually create an exception handling system. The problem is that the programmer would be responsible to setting up everything related to exceptions -- stack unwinding, catching exceptions, etc. Nobody complains about exceptions being a language feature instead of something programmers implement by hand -- so why not add another useful language feature?

Rather than callbacks, what you really want for restarts is continuations. One way a Lisp compiler might implement the Lisp "conditions" system would be something like this: convert the program to continuation passing style; each function takes two continuations, one that returns from the function (normal return) and one that is invoked when an exception is raised. Each function passes an exception handler continuation to the functions it calls; when a function declares an exception handler, it modifies the exception handler continuation to include its handler (the continuation would need to distinguish between exception types; this can be done any number of ways), which would include the previous exception handler continuation (so that exceptions can be propagated to higher levels). Exception handler continuations will take two continuations: one to unwind the stack (which is used to "return" from the handler), and the restarts continuation that is used for invoking restarts. When an exception is thrown, the thrower passes a restart continuation (or a continuation that throws some exception if no restarts are available), and then the handler continuation will either invoke the appropriate handler or it will invoke the handler continuation from the next higher level.

Complicated? Yes, and what I described above is just the simplified version. It should, however, be done automatically by the compiler, and the programmer should not even be aware that those extra continuation arguments are being inserted. The stack unwinding continuation could potentially be exposed to the programmer, and for convenience it could be set up to take a continuation as an argument -- either the rest of the handler, or else the "return from handler" continuation that exits the handler, so that the programmer could perform some error handling after the stack is unwound (e.g. the clean-up code), although this could potentially be accomplished using restarts (but that might be less "pretty").

Perhaps continuations should be suggested for C++14; it is an almost logical followup to the introduction to closures in C++11.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: