terminate() The Self Destruct Function

when things have  gone horribly wrong  and there  isn’t any graceful way of dealing with the problem,  the terminate()  function acts as a  last resort.

As its  name suggests, by default,  the remedy the method offers is simply ending the program. In the default implementation, it will do so by calling abort() Stack  unwinding or object destruction are not guaranteed – It is completely up to the compiler implementation. So if your code  depends on the object destructor for any cleanups,  such as erasing temporary files,  calling terminate() might leave you cleaning the mess on your own.

Obviously calling terminate () doesn’t make  a whole lot of sense in a normal process flow. However,  it is not difficult to inadvertently create a situation where it is called indirectly.   Depending on the compiler you are using and the build type (debug or production)  your process might simply  wink out of existence with no apparent reason.

In this post,  I’ll review a few scenarios with  C++ 14 where  throwing an exception at the wrong place and time invokes terminate().

Throwing an exception without a proper exception handler :

#include <exception>
#include <iostream>

struct MyException : public std::exception
{
};

class A
{
public:
	void f()
	{
		std::cout << "exception thrown";
		throw std::exception();
	}
};

int main()
{
	A a;
	try
	{
		a.f();
	}

	catch (MyException ex)
	{
		std::cout << "exception caught";
	}
	std::cout << "exiting main()";  //Program will exits before this line is exectuted 
	return 0;
}

In this example,  The program is throwing a base class exception object but catching a subclass. Since downcasting is not implicit  and there is no catch clause to handle an std::exception, the program  ends  by calling terminate().

Throwing an exception from a function declared with  noexcept:

A function with a  noexcept  specification  mustn’t throw an exception .  The stress here is on must not   there is nothing preventing it or any of the other function it’s calling  to throw, However, if the exception is not caught,  in the function scope,  terminate is called:

#include <exception>
#include <iostream>

class A
{
public:
	void g()
	{
		std::cout << "Throwing an exception" << std::endl;
		throw std::exception();
	}
	void f() noexcept
	{
		g();
	}
};

int main()
{
	A a;
	a.f();

	std::cout << "Called f()  exiting...";  //program terminats before getting here
	return 0;
}

Throwing an exception from a destructor:

User defined destructors are implicitly declared with noexcept ,   causing them to call terminate as described above.

throwing and exception in the constructor or the destructor  of a global, static or thread-local  objects:

Assuming the class :

class A
{
public:
	A()
	{
		std::cout << "Throwing from the constructor" << std::endl;
		throw std::exception();
	}
};

and any of these instantiations of A

A g_a;  // global
A MyClass::a  a3  //static
thread_local A a1; // thread local
int main()
{
	std::cout << "Main Started";
	return 0;
}

Will cause  the program to terminate before main() is even called.
Normally constructors are allowed to throw  exceptions. But statics, global and thread-local  objects are initialized before the main() function starts, and destroyed after it exits.  So exceptions thrown  while initializing  or destroying  these objects cannot  have an in-program code catching and handling making terminate() the only logical option.

Throwing an exception from a thread function:

Since a thread has its own stack,  an  exception handler clause  must exist in the context of  the thread to correctly unwind the stack once an exception is thrown.  So failing to have an exception handler in the thread function is similar to having no handler at all causing  the program to end with terminate():

#include <iostream>
#include <thread>

void threadFunc()
{
	std::cout << "Thread: Throwing an exception" << std::endl;
	throw std::exception();
}

int main()
{
	std::cout << "Main Started" << std::endl;
	try
	{
		std::thread t1(threadFunc);
		t1.join();  //
	}
	catch (std::exception ex)
	{
		std::cout << "exception caught" << std::endl; //not called
	}
	std::cout << "Main exiting..." << std::endl; // never called
	return 0;
}
Conclusion

Thre are other ways terminate can be unwittingly triggered. Such as changing the default std::atexit  handler to a user defined method and throwing and exception for it or re-trowing an exception you never received   But, in my view,  they are less  prone to happen without putting some effort into it.

For futher reading see  chapter 15.5.1 of  the C++ standard document:   C++14 –  ISO/IEC 14882:2014,  C++ 11 –  ISO/IEC 14882:2011

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *