C++ Exceptions

The Partridge Family were neither partridges nor a family. Discuss.
Post Reply
albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

C++ Exceptions

Post by albinopapa » November 8th, 2019, 9:41 pm

Perhaps you've heard of them, they are a way of halting a program dead in it's tracks if an irrecoverable error occurs. Say you have a program that depends on a specific file being loaded, but the file is lost or corrupted. There isn't a point in returning an error code since there's nothing you can do but tell the user about such a problem and they must reinstall the program. This is a perfect scenario for throwing an exception.

There are some warnings about exceptions though. One is pretty widely talked about or at least it's something I've heard many people bring up on C++ and exceptions is never let your destructors throw an exception. The reason for this is because when an exception is thrown, the program begins to unwind it's stack. This starts calling destructors for any variables declared locally all the way back to either the beginning of the program or the first catch block it comes across. If during that period of unwinding and calling destructors another exception is thrown the program is required to call terminate(). When this happens, the program quits without calling any more destructors and resources may or may not be released back to the operating system upon program exit.

Now, here's another interesting thing I just ran across. If a constructor throws, it's destructor is NOT called. This makes sense because it means the object is not fully formed so destruction may be meaningless. What happens though to open files or memory that's already been allocated? Well, quite frankly, they are dropped on the floor, gone forever, or until next reboot or maybe the OS will do the cleanup when program ends I'm not sure. I'll have to do some digging I suppose.

That being the case, things become difficult in determining how to handle constructing objects in my opinion. In the case above where your program relies on a file being there and not corrupt, you may want to start putting try/catch blocks around these types and if an exception is thrown, release any other resources that may have been acquired before the attempt at loading the failed resource.

I'll try coming up with examples and get back on some suggestions for handling such behavior. Just something to think about. Unfortunately, this makes delayed initialization more appealing.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

User avatar
cyboryxmen
Posts: 190
Joined: November 14th, 2014, 2:03 am

Re: C++ Exceptions

Post by cyboryxmen » November 9th, 2019, 1:54 am

Having resources leak in constructors because destructors don't run on a failed construction is a common issue that's brought up but it can easily be fixed. You just have to wrap every individual resource in its own ownership object. So instead of

Code: Select all

class X
{
    X()
    {
        arr1 = new int[1000];
        arr2 = new int[1000]; // Could throw and leak arr1
    };
    
    // ...

private:
    int* arr1;
    int* arr2;
};
you do

Code: Select all

class X
{
    X()
    {
        arr1 = std::unique_ptr<int[]>{1000};
        arr2 = std::unique_ptr<int[]>{1000}; // Can throw but arr1 won't leak since it is its own ownership object.
    };

private:
    std::unique_ptr<int[]> arr1;
    std::unique_ptr<int[]> arr2;
};
About not being able to throw in destructors, you can have a resource pool that keeps track of every allocated resource in the program. Constructors get the resources from the pool and destructors simply mark them for deletion. You can then call the resource pool's procedure at a specific point of the application to do mass clean up of all marked resources and fetch the cleanup errors from there. Now you can clean up your application properly and still use RAII!

Exceptions and RAII are just the best. Every time you allocate a resource , you always have to remember to deallocate them in proper order at the end. You can be in a rush to finish the job so you allocate the resource but forgot to clean it up. The program seems to run fine so you don't realise it until much later in the project when you somehow run out of resources. You also have to write an if statement around every single procedure that can throw(A.K.A all of them) to propagate their errors upwards and then have every function in the call chain propagate that upwards too. It's really adds up as your code gets bigger and I don't want to deal with that ever again.
Zekilk

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: C++ Exceptions

Post by albinopapa » November 9th, 2019, 2:30 am

Code: Select all

#include <iostream>
#include <memory>
struct A {
	A( int ) {}
	~A() {
		std::cout << "A::~A() called.\n";
	}
};
struct B {
	B() = default;
	B( int count )
	{
		up_a = std::make_unique<A>( count );
		throw std::runtime_error( "Out of memory" );
	}

	std::unique_ptr<A> up_a;

};

int main( int argc, char* argv[] )
{
	try
	{
		B a = B( std::numeric_limits<int>::max() );
	}
	catch( const std::exception& e )
	{
		std::cout << e.what() << '\n';
	}
	return 1;
}
Cpp.sh online compiler
And just to prove what cyboryxmen has just posted, here's a test. Ok, faith in constructors and exceptions restored, thanks bud.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: C++ Exceptions

Post by albinopapa » November 9th, 2019, 2:41 am

You can then call the resource pool's procedure at a specific point of the application to do mass clean up of all marked resources and fetch the cleanup errors from there.
So, what you are saying is:
The pool or resource locator handles instantiation of the resources
The objects needing the resources get passed these resources or a reference to the pool/locator and a pointer/reference to the resource is stored within the object
When the object is destroyed, it just sets the pointer to nullptr and somehow flags the resource as being not needed ( reference counted maybe? )
Then when the locator/pool is done and needs to clean up, it calls "release" functions instead of implicitly allowing the resource wrapper to destroy the resource?
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

User avatar
cyboryxmen
Posts: 190
Joined: November 14th, 2014, 2:03 am

Re: C++ Exceptions

Post by cyboryxmen » November 9th, 2019, 3:29 am

You only need a counter if it's a shared resource. If the ownership is exclusive like for std::unique_ptr, you just set a flag to mark it for deletion.
Zekilk

User avatar
chili
Site Admin
Posts: 3948
Joined: December 31st, 2011, 4:53 pm
Location: Japan
Contact:

Re: C++ Exceptions

Post by chili » November 10th, 2019, 4:44 am

Yeah, exceptions and RAII go hand-in-hand. Exceptions are also the only way to signal error directly from CTOR. So if you aren't using exceptions, and you want to signal failure, you have use delayed initialization as you mentioned. But doing so also compromises the integrity of your RAII.

"they are a way of halting a program dead in it's tracks if an irrecoverable error occurs"

I take issue with this sentence being the 1st part of your description of exceptions :lol: That is a really a useful side effect, and not the main purpose of exceptions.
Chili

Post Reply