Discussion:
C++ Exception and Copy Constructor requirement
(too old to reply)
Leon
2009-11-18 04:31:24 UTC
Permalink
Hi,

I know the C++ standard demands that when one throws an exception by value a
temporary copy of the exception object is being made and this temporary
object is passed to the catch site. The copy is made by the static type's
copy constructor.

I am fine with that.

However, the following experiments with VC2003, VC2005 and VC2008 produces
some interesting results and demands that I cannot explain. Perhaps the
community can provide some answer.

Using VC2003:
The compiler demands a public declaration of a copy constructor otherwise
the compiler will tell you that you cannot throw that exception. In the
absence of the implementation, the linker reports unresolved externals. Fair
enough.

Using VC2005 & VC2008
The linker demands an implementation of the copy constructor of the
exception, even if it is a private copy constructor. The compiler does not
complain at all.

Questions:
1) For all these built, the copy constructor is NEVER called. I put a trace
there and I put a break point in a non-trivial copy constructor. I cannot
see the trace nor the debugger breaking in the copy constructor. What is
happening?

2) What is the use of the implementation of a private copy constructor? The
only thing that can use it is a member function of the exception. The throw
statement cannot use it. So why did VC2005/VC2008 was happy with that?

Am I seeing the compiler optimizer at work?

Thanks.

Leon
Alex Blekhman
2009-11-18 08:23:02 UTC
Permalink
Post by Leon
The compiler demands a public declaration of a copy constructor
otherwise the compiler will tell you that you cannot throw that
exception. In the absence of the implementation, the linker
reports unresolved externals. Fair enough.
This is the correct behavior.
Post by Leon
Using VC2005 & VC2008
The linker demands an implementation of the copy constructor of
the exception, even if it is a private copy constructor. The
compiler does not complain at all.
Could you provide some code that exposes this problem? I cannot
reproduce it with my VC++2008. Here is the code I tried:

<code>
class X
{
public:
X() {}
~X() {}

private:
X(const X&);
};

int foo(int a)
{
if(a == 1)
throw X();

return a + 5;
}

int main()
{
int res = 0;
try
{
res = foo(rand() % 2);
}
catch(X x)
{
cout << &x;
}

return res;
}
</code>

Compiler correctly complains about inaccessible copy constructor.
If I comment out the copy c'tor declaration, then the code
compiles and runs fine as it should.
Post by Leon
1) For all these built, the copy constructor is NEVER called. I
put a trace there and I put a break point in a non-trivial copy
constructor. I cannot see the trace nor the debugger breaking in
the copy constructor. What is happening?
According to 15.1/5

<quote>
If the use of the temporary object can be eliminated without
changing the meaning of the program except for the execution of
constructors and destructors associated with the use of the
temporary object (12.2), then the exception in the handler can be
initialized directly with the argument of the throw expression.
</quote>

So, the behavior is similar to returning an object by value from a
function: compiler checks the availability of copy c'tor and
destructor, but can avoid actually calling them.
Post by Leon
2) What is the use of the implementation of a private copy
constructor? The only thing that can use it is a member function
of the exception. The throw statement cannot use it. So why did
VC2005/VC2008 was happy with that?
Friends of such class (both functions and classes) will be able to
create copies of the class. However, I should admit that I cannot
recall any useful case for this quirk.

Alex
Leon
2009-11-18 10:52:01 UTC
Permalink
Hi Alex,

Your catch code is actually incorrect and I hope that is a typo. If you
catch by value, it will definitely require another copy, thus the copy
cater. In Herb Sutter and Scott Meyers' publications, it is recommended to
throw by value, as you have done, and catch by reference. Never catch by
value as it will cause slicing and extra copying. I hope that was a typo.

<code>
My code is like this:
// Exception's
class CExcept {
WORD m_dwError;
public:
CExcept(WORD err) : m_dwError(err) { }
WORD Error() const { return m_dwError; }

private:
CExcept( const CExcept & from ) : m_dwError( from.m_dwError )
{ ATLTRACE( _T("Copy cater\n") ); }
private:
CExcept & operator=( const CExcept & rhs ); // no implementation
};

// main program
void Foo() {
throw CExcept(100);
}

void G() { Foo(); }

void Test() {
try {
G();
}
catch( CExcept &e ) {
ATLTRACE( _T("Exception caught\n") );
}
}

int _tmain( int argc, _TCHAR* argv[] ) {
Test();
return 0;
}
</code>

Now if you comment out the implementation of the private copy cater of
CExcept and include just a declaration, the standard way of blocking
copying, just like that for the operator=(), the compilation is still
successful. But the linker fails as it cannot find the implementation.

With the code above, you should not see the trace "Copy C'tor" after a run.
If you put a break point there, it does not stop. If that is the case, why
demands the implementation?

I have used protected constructor. I have used private cater to support
factory methods with more revealing names. But never has the need to use a
private copy cater.

That's the puzzling bit and I hope someone can shed some light on this. The
moral of the story is that your exception class must provide a copy cater,
otherwise the compiler provided one may not work as required.

Leon
Post by Alex Blekhman
Post by Leon
The compiler demands a public declaration of a copy constructor otherwise
the compiler will tell you that you cannot throw that exception. In the
absence of the implementation, the linker reports unresolved externals.
Fair enough.
This is the correct behavior.
Post by Leon
Using VC2005 & VC2008
The linker demands an implementation of the copy constructor of the
exception, even if it is a private copy constructor. The compiler does
not complain at all.
Could you provide some code that exposes this problem? I cannot reproduce
<code>
class X
{
X() {}
~X() {}
X(const X&);
};
int foo(int a)
{
if(a == 1)
throw X();
return a + 5;
}
int main()
{
int res = 0;
try
{
res = foo(rand() % 2);
}
catch(X x)
{
cout << &x;
}
return res;
}
</code>
Compiler correctly complains about inaccessible copy constructor. If I
comment out the copy c'tor declaration, then the code compiles and runs
fine as it should.
Post by Leon
1) For all these built, the copy constructor is NEVER called. I put a
trace there and I put a break point in a non-trivial copy constructor. I
cannot see the trace nor the debugger breaking in the copy constructor.
What is happening?
According to 15.1/5
<quote>
If the use of the temporary object can be eliminated without changing the
meaning of the program except for the execution of constructors and
destructors associated with the use of the temporary object (12.2), then
the exception in the handler can be initialized directly with the argument
of the throw expression.
</quote>
So, the behavior is similar to returning an object by value from a
function: compiler checks the availability of copy c'tor and destructor,
but can avoid actually calling them.
Post by Leon
2) What is the use of the implementation of a private copy constructor?
The only thing that can use it is a member function of the exception. The
throw statement cannot use it. So why did VC2005/VC2008 was happy with
that?
Friends of such class (both functions and classes) will be able to create
copies of the class. However, I should admit that I cannot recall any
useful case for this quirk.
Alex
Alex Blekhman
2009-11-18 16:34:51 UTC
Permalink
Never catch by value as it will cause slicing and extra copying.
I hope that was a typo.
No it wasn't a typo. I intentionally wrote it that way to
emphasize that copy constructor is required.
Now if you comment out the implementation of the private copy
cater of CExcept and include just a declaration, the standard
way of blocking copying, just like that for the operator=(), the
compilation is still successful. But the linker fails as it
cannot find the implementation.
Sounds like a bug to me. The following code should fail, but
compiles cleanly with VC++2008:

<code>
class X
{
public:
X() {}

private:
X(const X&) {}
};

int main()
{
try
{
throw X();
}
catch(X&)
{
}

return 0;
}
</code>
With the code above, you should not see the trace "Copy C'tor"
after a run. If you put a break point there, it does not stop.
If that is the case, why demands the implementation?
As far as exception object is concerned, throwing it by value is
no different from passing an object by value as a function
argument or returning it by value from a function with `return'
statement. In all these cases the compiler is allowed to eliminate
redundant copy. In any case, the compiler is required by the
Standard to check the availability of copy constructor as if
actual copying were performed. (See
http://en.wikipedia.org/wiki/Return_value_optimization for more
info.)
That's the puzzling bit and I hope someone can shed some light
on this.
It looks like a bug in the compiler. An exception object with
inaccessible (private or protected) copy constructor should not
pass compilation.

Alex
Leon
2009-11-19 00:12:07 UTC
Permalink
As far as exception object is concerned, throwing it by value is no
different from passing an object by value as a function argument or
returning it by value from a function with `return' statement. In all
these cases the compiler is allowed to eliminate
In fact, your comment is almost the title used for an item in Scott Meyer's
book "More Effective C++". He went on to explain the differences. A function
always returns to the caller's site (unless the function fails to return)
but an exception never returns to the caller's site. It returns to catch
site, which could be within the caller's function scope or miles away. Hence
the need for transport a temporary copy is necessary.
redundant copy. In any case, the compiler is required by the Standard to
check the availability of copy constructor as if actual copying were
performed. (See http://en.wikipedia.org/wiki/Return_value_optimization for
more info.)
This is the only conclusion I could come up with that a break point on the
copy constructor or trace is never invoked.

I guess the actual object is one that exception mechanism uses to dispatch
to the catch site, there is no point to create an exception object on the
stack and then using it to initialize the temporary object. Hence there
offers an opportunity to optimize that out and not needing the invocation of
copy c'tor, just the constructor will do.

I know the standard does not specify the memory for this temporary comes
from. But I will revisit $15.1 of the standard to see if it demands a copy
c'tor even if it is not used and whether it even allows private or protected
copy c'tor at all.
It looks like a bug in the compiler. An exception object with inaccessible
(private or protected) copy constructor should not pass compilation.
Yes, I agree. C++ compiler in VS2003 is the only version that follows this.
Alex
Leon
2009-11-19 00:37:12 UTC
Permalink
Sounds like a bug to me. The following code should fail, but compiles
Yes I agree with you that this is a bug. Here is the proof.

Here is the relevant comments from the ANSI Standard 1998 section 15.1.5:
<begin quote>
If the use of the temporary object can be eliminated without changing the
meaning of the program except for the execution of constructors and
destructors associated with the use of the temporary object (12.2), then the
exception in the handler can be initialized directly with the argument of
the throw expression. When the throw object is a class object, and the copy
constructor used to initialize the temporary copy is not accessible, the
program is ill-formed (even when the temporary object could otherwise be
eliminated). Similarly, if the destructor for that object is not accessible,
the program is ill-formed (even when the temporary object could otherwise be
eliminated).
<end quote>

In according to this section, if one specific a private or protected copy
constructor, that is inaccessible to outside the class as if a copying
process is required, and that should cause the code to be ill-formed and the
compiler should report the fault.

Leaving a copy c'tor out of the class will then allow the freebie compiler
generated copy constructor, which is public, to satisfy this standard
requirement.

Therefore the C++ compilers in VS2005 and VS2008 (and not sure if in VS2010
beta) are at fault.

The first comment gives unequivocal explanation why a break point or any
trace in the user supplied accessible copy constructor is never invoked.

Mystery solved.

Leon

Loading...