You're pretty quick to assume I don't. Believe me, I understand. I just don't agree that it's a good idea to mix up object lifetimes and execution context by using destructors to "magically" release locks etc. It never was. The mistakes were made years ago.
I'm not quick to judge (in this case). I'm judging after careful consideration, because I know the difference between good and bad patterns. The ones being hasty are those who mistake their own comfort level with something (often because they know nothing else) for actual merit.
C++ without deterministic destructors to manage lifetime would be a (fundamentally) different language.
Furthermore, also disagree with you that it would be a better language. C++ has many warts but object lifetime and RAII is amongst its strong suits (unarguably, I thought — yes, resource lifetime is complex, but it’s inherently so; C++ just makes the complexity explicit and handles it in a good way). Handling resource lifetime in languages with nondeterministic GCs can be such a pain that it has fundamental, detrimental impact on the architecture. Just look at .NET’s handling of `Dispose`. I’ve written a ton of .NET GUI code, and handling resource lifetime (in particular GDI+) is an absolute pain point, which is uniquely caused by the lack of deterministic object lifetime.
> C++ without deterministic destructors to manage lifetime would be a (fundamentally) different language.
The problem is that it's not usefully deterministic once you add in exceptions, shared pointers, move semantics, lambda captures, etc. Not to mention every perverse combination of those things. Yes, it's deterministic in the tautological sense that almost everything is deterministic given enough information, but I'm not sure that's enough even for the people who write the compilers. Even they have bugs related to misunderstanding this "deterministic" system. I certainly wouldn't want to make it less deterministic, and defer certainly doesn't make it so.
> resource lifetime is complex, but it’s inherently so
A certain amount of complexity is natural, a certain amount is spurious and self-inflicted. See above for some of the causes of that spurious complexity.
> C++ just makes the complexity explicit
Being explicit about memory management isn't a goal. C is even more explicit about these things. Does that make it a better language? Even in C++, new/delete is more explicit than most of the current idioms, and every book on modern C++ recommends against them. Explicit memory management should be a last resort, for the cases not handled cleanly by the language's other constructs, and those should be kept to a minimum. C++ has notably failed at that. Literally every other popular language except for C does better.
> Handling resource lifetime in languages with nondeterministic GCs can be such a pain
Do you really see no alternatives between dumping all memory-management complexity on the programmer and a full tracing GC? The authors of Objective C's automatic reference counting or Rust's borrow checker might take issue with that, as would the people who developed the memory-lifetime rules and infrastructure for all of the bigger older C codebases I mentioned in another comment. It is in fact possible for object lifetimes to be far more deterministic than in C++ as it exists today, and I for one think that would be a good thing.
> The problem is that it's not usefully deterministic once you add in exceptions, shared pointers, move semantics, lambda captures, etc.
No, it really is, even in the presence of the things you mentioned. That’s the whole point.
> Being explicit about memory management isn't a goal. […]
I get the impression that you’re confusing explicit and manual memory management. They’re not the same.
> Do you really see no alternatives between dumping all memory-management complexity on the programmer and a full tracing GC?
Of course I do, but the alternatives aren’t without their own problems. I’m curious how Rust will fare but Objective C’s ARC, while attractively simple, has performance implications, and then there’s the problem with cycles.
> The problem is that it's not usefully deterministic once you add in exceptions, shared pointers, move semantics, lambda captures, etc. Not to mention every perverse combination of those things.
It really is though, and I can't stress that enough. I feel you just throwing out keywords to make it sound more complex than it is.
I already mentioned "defer" as in Go or Zig. It's explicitly tied to scope exit, not object lifetime, so it avoids all of the problems inherent in confusing the two.
I didn't ask what language you would use. I asked what you would do in C++, given you're criticizing people for writing such code in the language. If you're put so much thought into this problem like you claim then surely you must have a better alternative in mind.
I don't care what people "would do" in C++ today, because my whole point is that C++ started down this bad path years ago. I'm not criticizing people for writing such code. I'm criticizing the standards-makers who made such hacks (seem) necessary. It's not about having put thought into it either. Lots of people had put plenty of thought into it when the various versions of C++ were standardized. This is about making the right choices from among the alternatives available, and that was not done.
Demanding a solution that is both applicable to C++ as it exists today and yet not in C++ today is demanding two contradictory things. It's demanding that the same thing both did and didn't happen. It's dishonest. You want a suggestion? Adopt "defer" for the next version of C++. It's the best we can do. We can't change the past, but we can learn from it if we don't get stuck trying to excuse past mistakes and attack those who point them out.
I'm not quick to judge (in this case). I'm judging after careful consideration, because I know the difference between good and bad patterns. The ones being hasty are those who mistake their own comfort level with something (often because they know nothing else) for actual merit.