johnluetke
johnluetke

Reputation: 3483

C++: Throwing an exception invokes the copy constructor?

We have an custom error class that is used whenever we throw an exception:

class AFX_CLASS_EXPORT CCLAError : public CObject

It has the following copy constructor defined:

CCLAError(const CCLAError& src) { AssignCopy(&src); } // (AssignCopy is a custom function)

It was originally written and compiled / linked with MSVC6 (Visual Studio 2003). I am in the process of doing necessary changes to get it to compile and link against MSVC8+ (VS 2008+)

When the msvc8 linker is invoked, i get the following error:

LNK2001: unresolved external symbol "private: __thiscall CObject::CObject(class CObject const &)" (??0CObject@@AAE@ABV0@@Z)

I understand what the error is telling me: no copy constructor is defined for some child of CObject, so its going all the way up the inheritance tree until it hits CObject, which as no copy constructor defined.

I first saw the error when compiling the library that defines and first throws a CCLAError, which is why I am proceeding as if that is the cause.

I was able to resolve the error by changing

throw CCLAError( ... )

to

throw new CCLAError( ... )

and

catch(CCLAError& e)
{
   throw e;
}

to

catch(CCLAError& e)
{
   throw;
}

However, I do not understand why re-throwing a caught exception would invoke the copy constructor. Am I missing somethnig completely obvious? Subsequently, why would removing the variable holding a reference to a caught exception cause the copy constructor to not be invoked?

Upvotes: 4

Views: 6514

Answers (6)

Happy Green Kid Naps
Happy Green Kid Naps

Reputation: 1673

It appears that you have misunderstood what re-throw means. When you do -

catch(CCLAError& e)
{
   throw e;
}

-- you are NOT rethrowing. Instead, as you have observed, you are indeed creating a copy of the new exception. Instead (again, as you have found for yourself), this is what is technically the correct way to re-throw:

catch(CCLAError& e)
{
   throw;
}

Read chapter 14 in Stroustrup's TC++PL (14.3.1 deals with rethrowing).

Also, you do NOT have to do -

throw new CCLAError( ... )

Instead, do -

throw CCLAError( ... )

-- like you were doing before, only receive by CONST reference (you cannot hold a reference to a temporary).

catch(const CCLAError &e)

Upvotes: 0

Mark Ransom
Mark Ransom

Reputation: 308111

When you throw an exception, the object you're throwing generally resides on the stack. The stack is getting cleaned up as part of the process of throwing, so the compiler must make a copy that can continue to live beyond that point.

When you throw an object with new, you're not throwing the actual object but you're throwing a pointer to the object. That means your catch block must also catch a pointer rather than a reference. Don't forget to delete the pointer or you'll have a memory leak!

When the catch block uses throw; instead of throw e; it can reuse the copy it made earlier and there's no need to make another copy.

Upvotes: 1

James McNellis
James McNellis

Reputation: 354979

The type of the object thrown must be copyable because the throw expression may make a copy of its argument (the copy may be elided or in C++11 a move may take place instead, but the copy constructor must still be accessible and callable).

Rethrowing the exception using throw; will not create any copies. Throwing the caught exception object using throw e; will cause a copy of e to be made. This is not the same as rethrowing the exception.

Your "updated" code does not work as you expect. catch (CCLAError&) will not catch an exception of type CCLAError*, which is the type of the exception thrown by throw new CCLAError(...);. You would need to catch CCLAError*. Do not do this, though. Throw exceptions by value and catch by reference. All exception types should be copyable.

Upvotes: 13

Artyom
Artyom

Reputation: 31233

Several points:

1st of all don't call throw new foo() use throw foo

2nd when your write:

catch(foo const &e) {
   throw e;
}

You actually create a new exception, and if for example the exception that was thrown is subclass of foo they my calling throw e you would loose this information and actually use a copy constructor to generate foo from e - whatever it was.

Now when you call

catch(foo const &e) {
   throw;
}

You don't create a new exception but rather propagate the same exception.

So: never use throw e; to propagate the exception forward, use throw;

Upvotes: 4

Sarfaraz Nawaz
Sarfaraz Nawaz

Reputation: 361262

The language specification allows the compilers to make as many copies of the original object as they want. There is no restriction on the number of copies.

That means, your custom exception class must have an accessible copy-constructor, either the compiler generated one, or user-defined one.

Upvotes: -1

K-ballo
K-ballo

Reputation: 81349

However, I do not understand why re-throwing a caught exception would invoke the copy constructor.

It doesn't, but re-throwing the thrown exception is done with throw;. When you do throw e; you are requesting to throw a copy of the caught exception.

Upvotes: 5

Related Questions