4 ms·
"If you unwind the stack after the catch block, the lock_guard destructor inside of lock_and_throw() gets executed after the mutex has already been deleted. So
by betterunix 14y ago
"If you unwind the stack after the catch block, the lock_guard destructor inside of lock_and_throw() gets executed after the mutex has already been deleted. So either the mutex_wrapper constructor can't delete m_ptr from the catch block (and then where is it supposed to do it when it wants to re-throw the exception?) or lock_and_throw can't assume that an otherwise valid argument passed to it won't be deleted out from under its stack variables' destructors whenever any exception is thrown."
That sounds like the sort of memory management problem that smart pointers are supposed to solve. It looks like a smart pointer would be exactly the sort of thing you would want here: when and if the stack is ultimately unwound, the lock_guard object will be destroyed first, then the smart pointer (because unwinding a constructor will cause the destructors of member objects to be invoked; note that your code throws the exception out of the constructor, and so the stack would have to be unwound at a higher level catch). The problem is not with unwinding the stack at the end of the catch block (which would not even be reached in your example, because of the throw in the catch block); the problem is that you explicitly deleted the pointer before the stack would have been unwound.
"And if you run the catch block and then go back and unwind the stack, it still leaves the question of what happens when a destructor throws during stack unwinding. Do you call the same catch block again (and then have to check for possible double free etc.), or do you let an exception that was surrounded by catch(…) escape out to the calling function just because it was already called once? Neither seems pleasant."
Really? It sounds like the second option would be what you would want: if the stack was unwound implicitly after the body of the catch block had executed, and unwinding the stack caused an exception to be thrown, then the exception was thrown out of the catch block. How is that unpleasant?
"The conclusion should be instead that global variables shouldn't be instantiated using constructors that throw exceptions unless program termination is the desired result when such an exception occurs -- which it actually is in most cases, because having uninitialized global variables floating around is unwise to say the least."
Except that a failure to initialize should at least be reported to the user. Maybe you could not get a network connection, or you could not allocate memory, or there is a missing file -- whatever it is, the user should know, and the thrower of the exception should not be responsible for telling the user. If you have restarts, you get something better -- you get the chance to try the operation again, which might be good if you have a long start-up process.
"Also, if it should ever become an actual problem in a specific instance, there is always the possibility of initializing the object with a different, non-throwing constructor and then having main reinitialize it on startup and catch any exceptions."
In other words, all classes should provide a non-throwing constructor and an initialization routine, because any object might be constructed in the global scope.
"There are obviously some applications that require arbitrary precision. But most applications never need to count as high as 2,305,843,009,213,693,952."
It is not just about counting high. If an application adds or multiplies two numbers, there is a chance of an overflow. If the application is reading one of the operands from the user, that overflow could be a security problem -- such problems are frequently reported.
"So why pay for it constantly if you so rarely need it?"
You don't have to pay for it constantly; you can have fixed-width types as something the programmer explicitly requests, or as something the compiler generates as an optimization. The real question is, why should the default type be the least safe, and why should programmers have to work harder to get a natural and safe abstraction?
"who says the compiler can't optimize out the arbitrary precision library calls in cases where the values are known at compile time just because the library isn't an official part of the language? The calls are likely to be short enough to be inlined and then if all the values are static the optimizer has a good shot at figuring it out."
Can you name a C++ compiler that does this?
"String literals as const char arrays are mostly harmless because the compiler ensures that they're "right" -- if they're const then you can't accidentally go writing past the end because you can't accidentally write at all"
You can accidentally read past the end, and you can accidentally print what you read. That can cause a lot of problems. There is no requirement that people use the standard library to iterate through a string.
"Moreover, bounds checking is pretty easy if you want it"
Once again, the programmer has to do extra work just to get something safe, because the default semantics are unsafe. If bounds checking is so easy, why not make it the default, and have unchecked access be an option for cases where speed matters? You already have at() and operator[] -- all that is needed is to switch which one of those does the bounds check.
- AnthonyMouse 14y ago>That sounds like the sort of memory management problem that smart pointers are supposed to solve. Using a smart pointer would solve that specific problem, but then you're de facto mandating the use of smart pointers in every code block that an exception could be thrown through, which is every code block. And what if mutex_wrapper is a smart pointer class? Do I now need to use a smart pointer to implement my smart pointer? Turtles all the way down? Or take operator new, which isn't an object at all so doesn't have a destructor, but still has to deallocate the memory it allocated if the constructor it calls throws, so it would need a "special" smart pointer object to use internally that only deallocates but doesn't call the destructor. It's not just calling destructors -- it's any cleanup operations because you can't do any destructive cleanup from a catch block anymore. So you end up requiring atomic RAII, and then none of the code actually implementing RAII can safely throw. It feels like just moving the problem: Now instead of not being able to throw from a destructor, you can't throw from code between resource allocation and turning on the destructor. Which is very close to saying you can't throw through a constructor. >Really? It sounds like the second option would be what you would want: if the stack was unwound implicitly after the body of the catch block had executed, and unwinding the stack caused an exception to be thrown, then the exception was thrown out of the catch block. How is that unpleasant? void foo() { std::vector<destructor_always_throws> v; v.reserve(1000); for(size_t i = 0; i < 1000; ++i) v.emplace_back(destructor_always_throws()); } Now I've got a thousand destructors that are going to throw one after the other as soon as the function returns. The nearest catch block will catch the first one, then go back to unwinding the stack where the next one throws. The next nearest catch block will catch that one and then go back to unwinding the stack again. Pretty sure I'm going to run out of catch blocks eventually, but I'd like to be able to catch all the destructor exceptions somehow and not end up with program termination, since that was supposed to be the whole idea. >Except that a failure to initialize should at least be reported to the user. Which it is: $ ./a.out terminate called after throwing an instance of 'std::bad_alloc' what(): std::bad_alloc Granted the message could be less cryptic, but we already know how to fix it. Don't call constructors that throw from global scope. It's pretty much the same thing as saying don't throw out of main(), because you get the same thing. >In other words, all classes should provide a non-throwing constructor and an initialization routine, because any object might be constructed in the global scope. Not any object, just the ones that have already been used that way thoughtlessly and need to be fixed without impacting existing code using the object. If you just need a global in new code (and you're insistent on a global), make it a pointer or smart pointer and then use operator new in main to initialize it. >If the application is reading one of the operands from the user, that overflow could be a security problem -- such problems are frequently reported. All user input needs to be validated. The language doesn't matter. If you're using a language that provides arbitrary precision, the user can instead provide you with a number which is a hundred gigabytes long and will take a hundred years for your computer to multiply. "If num > threshold then reject" is going to be necessary one way or another. >The real question is, why should the default type be the least safe, and why should programmers have to work harder to get a natural and safe abstraction? Because it's faster. The language is old, being fast was important then, and sometimes it still is today. But given that both types are available, isn't your complaint more with the textbooks that teach people to use the faster, less safe versions rather than the slower, safer versions by default? Or do you just not like that the trade off between runtime checking and performance is even allowed? >You can accidentally read past the end, and you can accidentally print what you read. That can cause a lot of problems. There is no requirement that people use the standard library to iterate through a string. There is no requirement that people don't write code that says "uid = 0" where it should say "uid == 0" either. People who write bad code write bad code. I understand that languages can and should help you avoid doing things like that, but at some point, when everybody says "don't do that" and you do it anyway, you get what you get. Most languages allow you to call C libraries from them, and if you call them wrong you get the same unsafe behavior. Does that make all those languages too unsafe to use too?