I agree, this is not how C++ handles thread abortion. I don't think such correctness guarantees can be achieved in C++ without turning to garbage collection (GC) techniques or binary instrumentation (BI) and changing the current semantics.
C++ takes advantage of RAII, hence, it is the responsibility of the constructor to acquire and initialize resources, conversely, it's the duty of the destructor to undo such operations.
Generally speaking, the destructor of an object is executed at the end of the scope where it has been allocated or when the object is explicitly deleted. The former corresponds to additional code that invokes the destructor; it is placed at the end of the current scope and is filled in by the compiler.
The OS' abortion function is generally not graceful.
Making it graceful in such a way that allocated objects are correctly freed is challenging. Notice it would require the help of the run-time execution support because the abortion should defer the invocation of the OS' primitive until all freeing has occurred.
Let's say we don't employ GC or BI, then it would require the execution of those pieces of code responsible for invoking the destructor, I think there are two possibilities, one applies at run-time, the other at compile-time:
- bookkeep ordered references to all allocated instances for each (possibly nested) scope; at abortion time delete all objects pointed by such references via their destructors (this will have problems with loopful data structures),
- rewire the execution flow in such a way that on abortion all code but the one responsible for invoking destructors is executed (impossible without some form of annotation).
The first proposal is a (badly) simplified version of GC.
The second proposal is probably impossible to achieve in a general setting, it would require rewiring all functions of the program in such a way that when the abort function is invoked the program switches to an "abortion mode" that simply disables the execution of the business logic while allowing to execute destructors. I think it is technically possible to achieve a language behaving this way but it wouldn't be C++.
Conversely, let's say we are up to employ GC or BI.
On one hand, let's say we wan to employ GC. C++ is a non-cooperative language, hence making it work with a GC would challenging but the collector would be able to free those objects.
On the other hand, let's say we want to employ BI. This technique requires running the compiled program in a VM and switching to the abortion mode when the abort function is called. One open problem remains: How do we reliably distinguish the machine code required for freeing from business code?
Lastly, I think we can agree that it is far simpler and more effective to write the code in such a way that in case an error occurs we behave accordingly; either it IS critical, hence there is nothing to do, or it is not and there is still room for safely cleaning the environment.
TerminateThread()and C++native_handle()$\endgroup$