Click here to Skip to main content
15,912,977 members
Articles / Programming Languages / C++
Tip/Trick

Initialization List Exceptions and Raw Pointers

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
3 Apr 2019MIT 2.2K  
Initialization List Exceptions and Raw Pointers

What to do when an exception is thrown on the initialization list when allocating memory for a raw pointer? The situation is easy if your class only has one raw pointer member, but it gets complicated with two or more. Here’s a code example that’s guaranteed to leak memory if the second...

C++
new int

...throws an exception (because the destructor will not be called):

C++
struct bad
{
	bad() : p1(new int), p2(new int)
	{
		*p1 = 1;
		*p2 = 2;
	}

	~bad()
	{
		delete p1;
		delete p2;
	}

	int* p1;
	int* p2;
};

There is no way to free the memory allocated to...

C++
p1

...if...

C++
p2(new int)

...throws! Let’s build on my previous example and see what happens if we use a function try-catch block on the constructor:

C++
struct still_bad
{
	still_bad() try : p1(new int), p2(new int)
	{
		*p1 = 1;
		*p2 = 2;
	}
	catch(...)
	{
		if(p1) delete p1; // ILLEGAL~!!!
		if(p2) delete p2; // ILLEGAL~!!!
		/*
		 * From: https://en.cppreference.com/w/cpp/language/function-try-block
		 *
		 * The behavior is undefined if the catch-clause of a function-try-block used on a
		 * constructor or a destructor accesses a base or a non-static member of the object.
		 */
	}

	~still_bad()
	{
		delete p1;
		delete p2;
	}

	int* p1 = nullptr;
	int* p2 = nullptr;
};

Still no good! Because accessing...

C++
p1

...and...

C++
p2

...in the catch block leads to undefined behavior. See here.

The only way to guarantee correct behavior is to use smart pointers. This works because:

  1. the initialization list allocates in pre-defined order (the order of member declaration) and
  2. the destructors of already created members will be called.

Here’s the correct way of allocating multiple pointers:

C++
struct good
{
	good() : p1(make_unique<int>()), p2(make_unique<int>())
	{
		*p1 = 1;
		*p2 = 2;
	}

	unique_ptr<int> p1;
	unique_ptr<int> p2;
};

This is guaranteed to do proper cleanup if the second...

C++
make_unique<int>()

...throws...

C++
std::bad_alloc

🙂

Complete Listing (bad_pointer.cpp)

C++
#include <iostream>
#include <memory>

using namespace std;

struct bad
{
	bad() : p1(new int), p2(new int)
	{
		*p1 = 1;
		*p2 = 2;
	}

	~bad()
	{
		delete p1;
		delete p2;
	}

	int* p1;
	int* p2;
};

struct still_bad
{
	still_bad() try : p1(new int), p2(new int)
	{
		*p1 = 1;
		*p2 = 2;
	}
	catch(...)
	{
		if(p1) delete p1; // ILLEGAL~!!!
		if(p2) delete p2; // ILLEGAL~!!!
		/*
		 * From: https://en.cppreference.com/w/cpp/language/function-try-block
		 *
		 * The behavior is undefined if the catch-clause of a function-try-block used on a
		 * constructor or a destructor accesses a base or a non-static member of the object.
		 */
	}

	~still_bad()
	{
		delete p1;
		delete p2;
	}

	int* p1 = nullptr;
	int* p2 = nullptr;
};

struct good
{
	good() : p1(make_unique<int>()), p2(make_unique<int>())
	{
		*p1 = 1;
		*p2 = 2;
	}

	unique_ptr<int> p1;
	unique_ptr<int> p2;
};

int main()
{
	bad b;
	still_bad sb;
	good g;
}

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --