Friday, December 15, 2006

Exceptions (part 2)

So we have seen on my last post that throwing exceptions and functions call have some similarities, we have also seen that throw an object always means copy it even if we catch for reference, as all copies in C++ are based on the static type then even in the exceptions environments the objects thrown are base on the static type.


Not always throw an exception assures you to avoid memory leakages even using "automatic objects", consider this example:



class Foo {
public:
Foo();
~Foo()
private:
AType* aPointer;
BType* bPointer;
};

Foo::Foo()
:aPointer(new AType),
bPointer(new BType)
{ }


we know that the initializer list order depends on the order declaration in the definition of class,
so in this case aPointer is initialized first then bPointer. What happens if the "new BType" throws an exception? Well, given the fact aPointer is a plain pointer then we will have memory leakage. So a first thought can be to use not plain pointers but something like "smart pointer".
So a first approach can be the following:



class Foo {
public:
Foo();
~Foo()
private:
std::auto_ptr aPointer;
std::auto_ptr bPointer;
};

Foo::Foo()
:aPointer(new AType),
bPointer(new BType)
{ }


well this is still not safe. Let see the constructor execution sequence:

1) Constructor AType is executed (new AType)
2) Constructor BType is executed (new BType)
3) Constructor std::auto_ptr is executed ( aPointer( ... ) )
4) Constructor std::auto_ptr is executed ( bPointer( ... ) )

do you see know where the problem is? If still "new BType" throws an exception the address of memory allocated by new AType was still not saved anywhere; unfortunately the correct way to solve this problem is the following:




class Foo {
public:
Foo();
~Foo()
private:
std::auto_ptr aPointer;
std::auto_ptr bPointer;
};

Foo::Foo()
:aPointer(),
bPointer()
{
aPointer = std::auto_ptr(new AType);
bPointer = std::auto_ptr(new BType);
}



throwing an exception can also leave the object in an inconsistent state, consider the following class (do not consider the fact that the class is useless):



class Foo {
public:
Foo()
:theStorage()
{ }

addInt(int anInteger) {
theStorage.push_back(anInteger);
}

void sumOne() {
int i;
for (i=0; i < theStorage.size(); ++i) {
theStorage[i] += 1;
if (i==2) {
throw std::runtime_error("OPS!");
}
}

private:
std::vector theStorage;
};



and his usage:



Foo aFoo;

aFoo.addInt(0);
aFoo.addInt(1);
aFoo.addInt(2);
aFoo.addInt(3);



at this point calling:



aFoo.sumOne();



will throw an exception leaving aFoo with partial updated elements, and from user point of view the aFoo is in an inconsistent state, so the sumOne() function shows here another problem that can break the exception safety of a class. The solution on this kind of problems is to work on a copy of internal class state and then make a swap between the internal state and the modified state.

No comments: