śr., 17 kwi 2019 o 16:32 Peter Dimov via Boost
Andrzej Krzemienski wrote:
However, you seem to be missing the distinction which I find very important: a "zombie" state that can only occur in *very special situations* (which correspond to the incorrect programs) is something substantially different than "zombie" states that can occur everywhere.
In a correct program using two-phase init, zombie states also cannot occur. The same rule is in effect: a correct program should never access a partially constructed object. There's nothing that makes the situation of a partially constructed object being observed any less special or *very special* than in what you suggest.
The distinction that I see is quite clear: In the case of two-phase init, you have to do nothing and the zombie object is observable. Usually these types have a default constructor. You construct, forget to initialize in some path (because you are catching exceptions too soon), and then you get a bug: because you failed to do something. In case of observing the state of the moved-from objects, or objects that threw from operation that guaranteed only an unspecified albeit valid state, you have to voluntarily do some action do get it. It cannot be done by omission: you have to put an explicit std::move or cast, and you know you are doing something potentially dangerous that requires special attention. Or, you have to put an explicit try-catch block, and you know you are doing something that requires special attention: because of stack unwinding many functions that were supposed to be called were canceled and now after the catch subsequent functions will be invoked and they may depend on the side effects of the functions that were canceled. In both this cases you have to voluntarily do something to get to this state. So the difference is: can you get to the zombie state by omission or can you get to the zombie state voluntarily. Another important difference is that in many cases a two-phase init class can be redesigned so that it is a RAII-like class, and then their users cannot make an omission bug. In case of moved-from state or unspecified (albeit valid) state, it is impossible to avoid the voluntarily put bugs: they can only be concealed. I will try to explain it later in this reply.
The problem is not one to be solved by putting asterisks around words. The problem, as I said, is that the rules governing correct programs are hard to enforce, and their violation is detected much too late, in parts of the program that have done nothing to violate the rules.
Yes. I acknowledge this as a serious problem.
With your rules, when the following very special situation throws:
x1 = std::move(x2);
you now have x1 singular, because no basic exception safety, and x2 singular, because moved-from. Now whether the program is correct or not depends on whether one of x1 or x2 escapes unscathed.
Yes, I think we agree up to this point. A correct program should make sure that if the above operation fails, unless we know either of the objects guarantees some concrete state after the throw, both objects should be removed from the scope or reset to a known state.
This is hard to diagnose statically - not that anyone has even tried -
Agreed: hard to diagnose, and it is to be expected that in real-life programs that are often incorrect this will happen.
and will not be diagnosed at runtime until two hours later an unrelated part of the program tries to access x1 or x2,
Yes: the symptoms may appear much later in unrelated parts of the program that themselves are correct.
in which case you'll have a crash ("fast fail"),
I disagree: in the worse case it will not be a crash, but a program will continue its operations and give an impression that it is working fine, but it will be doing something else than the programmer expected. A self driving car will be reporting that all the systems are functioning normally, but it will be crashing into people. This is fare worse than the application crash.
except it won't be fast, and it will tell you nothing about what caused it or who was at fault.
Yes.
There's really not that much difference between the above and
x1.init( f() );
except this one is easier to diagnose statically.
I have explained the difference above. So, let's now explore an alternative situation. x1 = std::move(x2); This throws, and leaves the objects in an unspecified (albeit "valid") state, an exception handling is stopped prematurely, so that the objects remain in scope and the programmer makes no attempt to put their values into a known state. The only guarantee we have now is that no operation on `x1` or `x2` will cause an UB. But we have no guarantee as to the correctness or consequences of this program. A program cannot be expected to work correctly if it uses unspecified values: you depend on the values of the variables, but you do not care if they were selected at random. In case of UB we at least had some probability that UB at some point would be detected and the program stopped (self driving car would report malfunction in one of its redundant systems), but now we have the guarantee that no malfunction will be reported and the program will be perceived as working correctly and the car will be crashing into people. So, I agree with you that the case with UB is bad. But the alternative -- random values instead of UB -- is even worse. If we map this situation onto the design of `variant`. Setting some default constructed T upon throw is considered, at least by me, the worst possible path. Leaving it in zombie state is better, although I acknowledge the FUD over UB. Providing strong or semi-strong guarantee is also a better solution: because in this case all my talk about objects surviving the stack unwinding no longer apply.
Again, I am not convinced that you are seeing the distinction between "zombie states in very special circumstances" and "zombie states everywhere". I agree with you on "zombie states everywhere".
You're constructing a strawman of your choosing and setting it on fire.
Peter, It is clear that you do not agree with me, but do some parts of what I say make sense to you? Regards, &rzej;