Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Initialization List Exceptions and Raw Pointers

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