Exploding return codes.

115 views
Skip to first unread message

Ken Hagan

unread,
Feb 11, 2000, 3:00:00 AM2/11/00
to
Out of intellectual curiousity, I wrote the following class.

class ReturnCode
{
long m_rc;
public:
~ReturnCode() { if (m_rc<0) throw m_rc; }
ReturnCode(long rc) : m_rc(rc) { }
operator long () { long rc = m_rc; m_rc=0; return rc; }
};

This class is either assigned to a "long", or throws an exception
when it goes out of scope. The idea is to use it as a function return
type. People who don't want to check the return value get an exception
thrown instead, so no errors are *accidentally* missed.

I hoped the compiler would perform the following optimisations.

1 That objects of this class would be returned in a register, just like
a 32-bit integer.
2 That in the case where the return value is saved (and operator
long is invoked) the compiler would see that the destructor was
a no-op, and generate no code for it.

Using MSVC 6.3 at maximum optimisation, I observed neither. It
seems that all user defined types are returned from functions by
"reference to hidden parameter", and that the compiler fails to spot
that it has just written a zero to "m_rc" so it can't possible be
negative. I can think of three explanations.

A The proposed optimisations are unsafe in certain circumstances,
so no "good" compiler would ever do what I wanted.
B The proposed optimisations would generally be slower or more
bloated, and so the compiler never tries this kind of thing.
C Microsoft are mere mortals. Another compiler might do better.

So, er, which is it? Am I being totally unreasonable to expect this
level of optimisation? (Has anyone else used exploding returns?
If so, did they help or hinder in practice?)

[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]


Rocky Raccoon

unread,
Feb 12, 2000, 3:00:00 AM2/12/00
to
Ken Hagan wrote:
>
> Out of intellectual curiousity, I wrote the following class.
>
> class ReturnCode
> {
> long m_rc;
> public:
> ~ReturnCode() { if (m_rc<0) throw m_rc; }
> ReturnCode(long rc) : m_rc(rc) { }
> operator long () { long rc = m_rc; m_rc=0; return rc; }
> };
>

I assume you are proposing something like.

Instead of
long f()
{
case GOOD : return 1;
case BAD : return -1
case BAD1 : return -2;
// etc.
}

Use
long f()
{
case GOOD : return ReturnCode(1);
case BAD : return ReturnCode(-1);
case BAD1 : return ReturnCode(-2);
// etc.
}

How is this better than

long f()
{
case GOOD : return 1;
case BAD : throw Exp1
case BAD1 : throw Exp2;
// etc.
}

This will also prevent errors accidentally being missed.

--
regards,
Shiva

http://www.slack.net/~shiva/

Francis Glassborow

unread,
Feb 12, 2000, 3:00:00 AM2/12/00
to
In article <newscache$lxrrpf$qmd$1...@firewall.thermoteknix.co.uk>, Ken
Hagan <K.H...@thermoteknix.co.uk> writes

>Out of intellectual curiousity, I wrote the following class.
>
> class ReturnCode
> {
> long m_rc;
> public:
> ~ReturnCode() { if (m_rc<0) throw m_rc; }
> ReturnCode(long rc) : m_rc(rc) { }
> operator long () { long rc = m_rc; m_rc=0; return rc; }
> };
>
>This class is either assigned to a "long", or throws an exception
>when it goes out of scope. The idea is to use it as a function return
>type. People who don't want to check the return value get an exception
>thrown instead, so no errors are *accidentally* missed.

Interesting idea. What does the compiler do with:

class ReturnCode {
long rc_m;
bool ignored;
public:
ReturnCode(long rc) : rc_m(rc), ignored(true) {}
~ReturnCode(){ if (ignored) throw rc_m;}
operator long () {ignored = false; return rc_m;}
};

Is this a rare exception to the rule 'Do not throw exceptions from
destructors' ?


Francis Glassborow Journal Editor, Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation

Anatoli Tubman

unread,
Feb 14, 2000, 3:00:00 AM2/14/00
to
Francis Glassborow wrote:
> Interesting idea. What does the compiler do with:
>
> class ReturnCode {
> long rc_m;
> bool ignored;
> public:
> ReturnCode(long rc) : rc_m(rc), ignored(true) {}
> ~ReturnCode(){ if (ignored) throw rc_m;}
> operator long () {ignored = false; return rc_m;}
> };

Compiles it. :)

>
> Is this a rare exception to the rule 'Do not throw exceptions from
> destructors' ?

I don't think so. If you happen to ignore more than one return code,
and two such functions are currently active, the program will
terminate().

This is a programmer's error, and IMHO the best resolution
in this case is to call abort(), not throw any exceptions.

If you can't afford to terminate the program,
issue a debug message and continue as usual, hoping
that nothing bad will happen (but the program may
terminate regadless).

This (http://216.149.30.162/Resources/gotw047a.html)
explains why throwing from destructors is evil.

I *think* one can develop a framework that allows throwing
from destructors. Such a framework would involve encapsulating
throw/try/catch. The problem with it is that it would be
either ugly (pairs of macros for try/catch) or pain in the
rear to use (functions/function-objects instead of try/catch
blocks).
--
Regards
Anatoli (anatoli<at>ptc<dot>com) opinions aren't


Sent via Deja.com http://www.deja.com/
Before you buy.

Christopher Eltschka

unread,
Feb 16, 2000, 3:00:00 AM2/16/00
to
Rocky Raccoon wrote:

>
> Ken Hagan wrote:
> >
> > Out of intellectual curiousity, I wrote the following class.
> >
> > class ReturnCode
> > {
> > long m_rc;
> > public:
> > ~ReturnCode() { if (m_rc<0) throw m_rc; }
> > ReturnCode(long rc) : m_rc(rc) { }
> > operator long () { long rc = m_rc; m_rc=0; return rc; }
> > };
> >
>
> I assume you are proposing something like.
>
> Instead of
> long f()
> {
> case GOOD : return 1;
> case BAD : return -1
> case BAD1 : return -2;
> // etc.
> }
>
> Use
> long f()
> {
> case GOOD : return ReturnCode(1);
> case BAD : return ReturnCode(-1);
> case BAD1 : return ReturnCode(-2);
> // etc.
> }

I rather think he proposes:

ReturnCode f()
{
case GOOD: return 1; // implicit conversion
case BAD: return -1;
case WORSE: return -2;
}

>
> How is this better than
>
> long f()
> {
> case GOOD : return 1;
> case BAD : throw Exp1
> case BAD1 : throw Exp2;
> // etc.
> }
>
> This will also prevent errors accidentally being missed.

The difference is that with the definition above, the
exception will only be thrown if the caller doesn't check
the error return:

void foo()
{
long rc = f(); // won't throw
if (rc < 0)
print_error(rc);
// ...
}

void bar()
{
f(); // will throw on error
// ...
}

I like the idea.

Christopher Eltschka

unread,
Feb 16, 2000, 3:00:00 AM2/16/00
to
Francis Glassborow wrote:
>
> In article <newscache$lxrrpf$qmd$1...@firewall.thermoteknix.co.uk>, Ken
> Hagan <K.H...@thermoteknix.co.uk> writes
> >Out of intellectual curiousity, I wrote the following class.
> >
> > class ReturnCode
> > {
> > long m_rc;
> > public:
> > ~ReturnCode() { if (m_rc<0) throw m_rc; }
> > ReturnCode(long rc) : m_rc(rc) { }
> > operator long () { long rc = m_rc; m_rc=0; return rc; }
> > };
> >
> >This class is either assigned to a "long", or throws an exception
> >when it goes out of scope. The idea is to use it as a function return
> >type. People who don't want to check the return value get an exception
> >thrown instead, so no errors are *accidentally* missed.
>
> Interesting idea. What does the compiler do with:
>
> class ReturnCode {
> long rc_m;
> bool ignored;
> public:
> ReturnCode(long rc) : rc_m(rc), ignored(true) {}
> ~ReturnCode(){ if (ignored) throw rc_m;}
> operator long () {ignored = false; return rc_m;}
> };
>
> Is this a rare exception to the rule 'Do not throw exceptions from
> destructors' ?

Well, after thinking about it, there is one situation where
this code could create a double exception situation, even if
used only as intended:

ReturnCode f();
ReturnCode g();

void foo()
{
f(), g();
}

Now in foo() two temporary ReturnCode objects are created,
and not released until the end of expression, so there are
two existing ReturnCode objects.
Now if f() and g() both fail, you get a double exception.
Moreover, g() will be called despite f() having failed.
OTOH, one could add

struct ignore
{
ignore(ReturnCode) {}
}

template<class T>
T& operator,(ReturnCode f, T&); // not implemented

and then write:

void foo()
{
(ignore)f(), (ignore)g();
}

This way, the argument of ignore::ignore will be destructed, and
will throw at the correct point. Writing f(), g() isn't possible
any more (the linker will complain), and not writing (ignore)
in front of g() is harmless (if f() has failed, g won't be
executed).

I don't think there's another binary operator where the return
value of an evaluated expression is ignored, so the above should
be sufficient.

(If operator, wouldn't loose it's sequence point, we could even
forget about ignore and implement operator, the obvious way.
Given that - unlike && and || - operator, does always evaluate
both operands, is there any reason why the standard removes that
sequence point for user-defined operator,?)

Lisa Lippincott

unread,
Feb 17, 2000, 3:00:00 AM2/17/00
to
Christopher Eltschka <celt...@physik.tu-muenchen.de> suggests:
> AND make ReturnCode roughly auto_ptr like with the following
> copy constructor:
>
> ReturnCode::ReturnCode(ReturnCode const& other):
> m_rc(other.m_rc)
> {
> other.m_rc = 0; // prevent original one from throwing!
> }
>
> Of course this means that m_rc must be mutable.

Let's not repeat the mistakes of the past. Model the copy-construction
and assignment on the current auto_ptr, not the old broken one.

--Lisa Lippincott

Ken Hagan

unread,
Feb 17, 2000, 3:00:00 AM2/17/00
to
"Ken Hagan" <K.H...@thermoteknix.co.uk> wrote in message
news:newscache$lxrrpf$qmd$1...@firewall.thermoteknix.co.uk...

> I hoped the compiler would perform the following optimisations.
>
> 1 That objects of this class would be returned in a register, just like
> a 32-bit integer.
> 2 That in the case where the return value is saved (and operator
> long is invoked) the compiler would see that the destructor was
> a no-op, and generate no code for it.

I think I now understand why the compiler can't return the value in a
register. 4-byte PODs are returned in a register (as the compiler docs
state), but this data type has a destructor, so it has to be unwound in
the event of an exception from somewhere else. Given Microsoft's
table-driven unwinding, that means the object has to have an address,
and cannot simply have a fleeting existence in the registers.

This may also explain why it cannot remove the unreachable code. In
the presence of exceptions, it *is* reachable, even though the return
value is saved in a variable.

To optimise the code as I'd hoped, the compiler would have to prove
that no exceptions could occur between the creation of the return
value (in the callee) and the assignment to my variable. That is not
possible when compiling the caller, since the return value is created
before the local variables in the callee are destroyed, and (in general)
those destructors may throw. (Declaring the callee with throw() doesn't
help because the compiler isn't allowed to trust that specification.)

My conclusion is that the class cannot be reduced to zero-overhead
and so should be confined to "debug mode". Given that, it should
probably assert rather than throw, since we are no longer using it to
handle run-time errors but are simply catching coding errors.

Ken Hagan

unread,
Feb 19, 2000, 3:00:00 AM2/19/00
to
I'm still uncertain about whether the double exception is worth worrying
about (!). Remember, that I'm assuming we have a really good compiler
here (since I need the optimisations to remove dead code and to make
it binary compatible with returning a long). In those circumstances...

The compiler can see the risk of a "double-fault" because the destructor
is not qualified with "throw()" AND we destroy more than 1 such object
at end-of-expression. Therefore, a good compiler can issue a warning.

Actually, I'd be happy for the compiler to issue an error. Without wishing
to re-ignite the "static checking of exception specs" debate, I think the
empty specification ("throw()") is much more useful than a non-empty
one and its violation ought to be caught at compile time. (If anyone is
moved to reply on that point, I think it probably deserves its own thread.)

ka...@gabi-soft.de

unread,
Feb 21, 2000, 3:00:00 AM2/21/00
to
"Ken Hagan" <K.H...@thermoteknix.co.uk> writes:

|> Out of intellectual curiousity, I wrote the following class.

|> class ReturnCode
|> {
|> long m_rc;
|> public:
|> ~ReturnCode() { if (m_rc<0) throw m_rc; }
|> ReturnCode(long rc) : m_rc(rc) { }
|> operator long () { long rc = m_rc; m_rc=0; return rc; }
|> };

|> This class is either assigned to a "long", or throws an exception

|> when it goes out of scope. The idea is to use it as a function =
return
|> type. People who don't want to check the return value get an =


exception
|> thrown instead, so no errors are *accidentally* missed.

One comment on the implementation: you should add a copy constructor
which copies the value and sets the source to read. This allows (quite
reasonalbe) things like:

ReturnCode f() ;

ReturnCode g()
{
// ...
return f() ;
}

Other than that, our implementation used an assertion check in the
destructor, rather than throwing an exception. (Some of our compilers
at the time didn't support exceptions.)

|> I hoped the compiler would perform the following optimisations.

Dream on. I've heard about these optimizations since I first started
C++, but I've yet to see them in reality.

|> 1 That objects of this class would be returned in a register, just =


like
|> a 32-bit integer.
|> 2 That in the case where the return value is saved (and operator
|> long is invoked) the compiler would see that the destructor was
|> a no-op, and generate no code for it.
|>

|> Using MSVC 6.3 at maximum optimisation, I observed neither. It
|> seems that all user defined types are returned from functions by
|> "reference to hidden parameter", and that the compiler fails to spot
|> that it has just written a zero to "m_rc" so it can't possible be
|> negative. I can think of three explanations.
|>
|> A The proposed optimisations are unsafe in certain circumstances,
|> so no "good" compiler would ever do what I wanted.
|> B The proposed optimisations would generally be slower or more
|> bloated, and so the compiler never tries this kind of thing.
|> C Microsoft are mere mortals. Another compiler might do better.

Another compiler might do better. But I don't actually know of one that
does.

|> So, er, which is it? Am I being totally unreasonable to expect this
|> level of optimisation? (Has anyone else used exploding returns?
|> If so, did they help or hinder in practice?)

I've used such returns in large scale projects. We carefully designed
the class so that it could be replaced with a typedef to int, without
any changes in user code (but without the control, obviously). As it
happened, this wasn't necessary in our application, and we shipped with
the error checking in. I still think that the possibility of
typedef'ing to int is a good choice, just in case.

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique oriente objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

ka...@gabi-soft.de

unread,
Feb 22, 2000, 3:00:00 AM2/22/00
to
Lisa Lippincott <lisa_li...@bigfix.com> writes:

|> Christopher Eltschka <celt...@physik.tu-muenchen.de> suggests:
|> > AND make ReturnCode roughly auto_ptr like with the following
|> > copy constructor:

|> > ReturnCode::ReturnCode(ReturnCode const& other):
|> > m_rc(other.m_rc)
|> > {
|> > other.m_rc = 0; // prevent original one from throwing!
|> > }

|> > Of course this means that m_rc must be mutable.

|> Let's not repeat the mistakes of the past. Model the =


copy-construction
|> and assignment on the current auto_ptr, not the old broken one.

I don't think it necessary. Or does someone really think there is a
possibility of someone wanting to declare a vector< ReturnCode >.

In practice, ReturnCode will only appear as the type of a function
return and as temporaries. In no case, thus, do we have the problem of
the state of the copied ReturnCode.

ka...@gabi-soft.de

unread,
Feb 22, 2000, 3:00:00 AM2/22/00
to
"Ken Hagan" <K.H...@thermoteknix.co.uk> writes:

|> "Ken Hagan" <K.H...@thermoteknix.co.uk> wrote in message
|> news:newscache$lxrrpf$qmd$1...@firewall.thermoteknix.co.uk...

|> > I hoped the compiler would perform the following optimisations.
|> >

|> > 1 That objects of this class would be returned in a register, =
just like


|> > a 32-bit integer.
|> > 2 That in the case where the return value is saved (and operator

|> > long is invoked) the compiler would see that the destructor =


was
|> > a no-op, and generate no code for it.
|>

|> I think I now understand why the compiler can't return the value in =
a
|> register. 4-byte PODs are returned in a register (as the compiler =
docs
|> state), but this data type has a destructor, so it has to be unwound =


in
|> the event of an exception from somewhere else. Given Microsoft's

|> table-driven unwinding, that means the object has to have an =


address,
|> and cannot simply have a fleeting existence in the registers.

A typical example of why exceptions may have a negative effect on
optimization:-). On the other hand, I don't know of any compiler which
made the optimization before exceptions, either.

ka...@gabi-soft.de

unread,
Feb 22, 2000, 3:00:00 AM2/22/00
to
"Ken Hagan" <K.H...@thermoteknix.co.uk> writes:

|> After having this idea, I read James Kanze in another thread
|> suggesting a return code class that lets you check whether it
|> has been ignored. I also receiving some E-mail from Lisa Lippincott
|> with the same idea. I suspect they were both intending to assert
|> rather than throw, and wished to consider the ignored return code
|> as a programming oversight to be fixed in the source code, rather
|> than a choice to be left in at run-time. They may be right.

Correct. In our case, the compiler didn't support throw anyway, so
assert was the only option. Today, I might well accept a throw in a
destructor here, on the grounds that the only alternative is to abort,
so even if the throw occurs during the stack walkback of exception
handling, it won't be any worse than the alternatives.

Obviously, by the time the code is delivered, the throw never will, so
there shouldn't be any problems:-).

Christopher Eltschka

unread,
Feb 23, 2000, 3:00:00 AM2/23/00
to
Lisa Lippincott wrote:
>
> Christopher Eltschka <celt...@physik.tu-muenchen.de> suggests:
> > AND make ReturnCode roughly auto_ptr like with the following
> > copy constructor:
> >
> > ReturnCode::ReturnCode(ReturnCode const& other):
> > m_rc(other.m_rc)
> > {
> > other.m_rc = 0; // prevent original one from throwing!
> > }
> >
> > Of course this means that m_rc must be mutable.
>
> Let's not repeat the mistakes of the past. Model the copy-construction

> and assignment on the current auto_ptr, not the old broken one.

ReturnCode has a different purpose than auto_ptr. Especially
you are not supposed to use objects of this type anywhere
except as return code. Especially all ReturnCode objects
are intended to be rvalues.

Siemel B. Naran

unread,
Feb 23, 2000, 3:00:00 AM2/23/00
to
On 21 Feb 2000 18:18:09 -0500, ka...@gabi-soft.de <ka...@gabi-soft.de> wrote:

>|> I hoped the compiler would perform the following optimisations.
>

>|> 1 That objects of this class would be returned in a register, just =


>|> like
>|> a 32-bit integer.
>|> 2 That in the case where the return value is saved (and operator

>|> long is invoked) the compiler would see that the destructor was


>|> a no-op, and generate no code for it.

>Dream on. I've heard about these optimizations since I first started


>C++, but I've yet to see them in reality.

Have you seen KAI KCC.

--
--------------
siemel b naran
--------------

Runu Knips

unread,
Feb 23, 2000, 3:00:00 AM2/23/00
to
Ken Hagan schrieb:

> Out of intellectual curiousity, I wrote the following class.
> [...]

> This class is either assigned to a "long", or throws an exception
> when it goes out of scope. The idea is to use it as a function return
> type. People who don't want to check the return value get an exception
> thrown instead, so no errors are *accidentally* missed. [...]

Well thats a funny class. But I'm not surprised that you didn't
wrote it for practical use !

(a) Why isn't it a template ? Would be more logical in my eyes.

(b) When you throw an exception if the error is missed why don't
you throw an exception instead of using error return values ?
(oops many people wrote that before)

(c) Why do you look at the compiler output at all ? ;-)

Any optimization I've ever guessed had to be done where not
done by any compiler I've checked. Compilers just do those
optimizations which occur most often, and don't believe
there are people which write macros like the following:

struct __synchronize_vars {
pthread_mutex_t *pmutex;
bool once;
__synchronize_vars (pthread_mutex_t& m)
: pmutex(&m), once(false) {}
};
#define synchronized(mutex) \
for (__synchronize_vars CONCAT(__synchronize_vars_, __LINE__)
(mutex); \
! CONCAT(__synchronize_vars_, __LINE__).once; \
CONCAT(__synchronize_vars_, __LINE__).once = true)

When I wrote this, I was so proud that I've found a way
to use synchronized() in future - but guess what: (a) not
all compilers like variable declarations in the for clause,
(b) at least g++ doesn't optimize the loop away [hmm maybe
the current release does it ?!?].

Lisa Lippincott

unread,
Feb 23, 2000, 3:00:00 AM2/23/00
to
Christopher Eltschka <celt...@physik.tu-muenchen.de> suggested:

> AND make ReturnCode roughly auto_ptr like with the following
> copy constructor:
>
> ReturnCode::ReturnCode(ReturnCode const& other):
> m_rc(other.m_rc)
> {
> other.m_rc = 0; // prevent original one from throwing!
> }

I replied:


> Let's not repeat the mistakes of the past. Model the
> copy-construction and assignment on the current auto_ptr,
> not the old broken one.

But James Kanze <ka...@gabi-soft.de> questions this:


> I don't think it necessary. Or does someone really think there is
> a possibility of someone wanting to declare a vector< ReturnCode >.
>
> In practice, ReturnCode will only appear as the type of a function
> return and as temporaries. In no case, thus, do we have the problem
> of the state of the copied ReturnCode.

It may not be necessary. Maybe no one ever writes code which
expects const ReturnCodes not to change value. But, lacking your
faith in this matter, I prefer the design which works correctly
in the wider range of circumstances.

--Lisa Lippincott

Reply all
Reply to author
Forward
0 new messages