Destructor call of virtual base class - what happens with exception spec?

  • Thread starter Johannes Schaub (litb)
  • Start date
J

Johannes Schaub (litb)

I tried to force programmers of a derived class to explicitly define a
constructor (just for fun). This is what i came up with:

struct Viral {
struct Dose { };
protected:
~Viral() throw (Dose) { }
};

struct Base : virtual Viral {
virtual ~Base() throw() { }
};

struct Derived : Base { };

It's an illegal program because Derived::~Derived calls ~Viral, so it
includes (Dose) in its exception spec, but as it overrides ~Base, it will
have a looser exception spec and is this illegal. The user explicitly needs
to overrideit

struct Derived : Base { ~Derived() throw() { } }; // OK

Clang rejects the code with the implicitly defined destructor, but GCC and
comeau online accept it, which surprises me.

Are clang and me right, or are we wrong and the other two are right?
 
G

Gil

I tried to force programmers of a derived class to explicitly define a
constructor (just for fun). This is what i came up with:

struct Viral {
struct Dose { };
protected:
~Viral() throw (Dose) { }

};

struct Base : virtual Viral {
virtual ~Base() throw() { }

};

struct Derived : Base { };

It's an illegal program because Derived::~Derived calls ~Viral, so it
includes (Dose) in its exception spec, but as it overrides ~Base, it will
have a looser exception spec and is this illegal. The user explicitly needs
to overrideit

struct Derived : Base { ~Derived() throw() { } }; // OK

Clang rejects the code with the implicitly defined destructor, but GCC and
comeau online accept it, which surprises me.

Are clang and me right, or are we wrong and the other two are right?

MIOS (my interpretation of standard): you are right;
in your example, implicitly declared Derived::~Derived should come
with an implicit exception specification that is against 15.4/3 with
regard to overriding Base::~Base.
 
K

Kai-Uwe Bux

Balog said:
"Johannes Schaub (litb)"

Does it matter when there is so wide consensus that dtors shall not throw?

Note that none of the constructors above throws. One of them just has a
throw specification that "reserves the right" to throw. The constructor as
shown, however, does not.

I don't know whether there is a consensus about throw-specifications of
constructors.
And almost that wide to not write exceptions specs?

Sure it matters. Since two compilers disagree on whether the code is well-
formed, we want to know _where_ to file a bug report :)
Who would allow those fragments into any real program? I'd definitely not.

Well, his trickery about throw specifications (once thoroughly understood)
is about making the compiler barf at certain inputs and thereby forcing the
client of some library to code in a certain way. Such techniques, per se,
might prove useful in some circumstances; and I cannot rule out that the
"throw-spec-trick" could become part of such a technique.


Best

Kai-Uwe Bux
 
B

Balog Pal

Kai-Uwe Bux said:
Note that none of the constructors above throws. One of them just has a
throw specification that "reserves the right" to throw. The constructor as
shown, however, does not.

I don't know whether there is a consensus about throw-specifications of
constructors.

I was talking about destructors, not constructors. ctors are the best
candidates to throw, as other options to signal a problem are limited or
problematic.

While exceptions escaping dtors can wreak much havoc so many many template
libraries, including STL just call it UB if the subject does so.

Handling the consequences is problematic too -- and the most evil thing is
that call of other destructors gets prevented, killing the ctor/dtor
symmetry most good design builds on in a C++ program.
Sure it matters. Since two compilers disagree on whether the code is well-
formed, we want to know _where_ to file a bug report :)

LOL. Certainly I agree that the standard's verdict is better be clear, and
implementations do it corectly. :) Just IIRC exception spec is already
deprecated in c++0x -- and some widely used implementations deliberately
make it for different semantics.
Well, his trickery about throw specifications (once thoroughly understood)
is about making the compiler barf at certain inputs and thereby forcing
the
client of some library to code in a certain way. Such techniques, per se,
might prove useful in some circumstances; and I cannot rule out that the
"throw-spec-trick" could become part of such a technique.

Ah, I didn't think about such use. But it seems practice makes its
usability too limited for real practice.
 
J

James Kanze

"Johannes Schaub (litb)"

[...]
Does it matter when there is so wide consensus that dtors
shall not throw?

There is wide consensus that destructors usually should not
throw. There is even wider consensus that every rule may have
exceptions, and I have at least one class whose destructor
always throws.
And almost that wide to not write exceptions specs?

Except for empty ones.
 
V

Vladimir Jovic

James said:
"Johannes Schaub (litb)"
[...]
Does it matter when there is so wide consensus that dtors
shall not throw?

There is wide consensus that destructors usually should not
throw. There is even wider consensus that every rule may have
exceptions, and I have at least one class whose destructor
always throws.

Sounds like a hack. Can you explain why it always throws?
 
J

Johannes Schaub (litb)

Vladimir said:
James said:
"Johannes Schaub (litb)"
[...]
Does it matter when there is so wide consensus that dtors
shall not throw?

There is wide consensus that destructors usually should not
throw. There is even wider consensus that every rule may have
exceptions, and I have at least one class whose destructor
always throws.

Sounds like a hack. Can you explain why it always throws?

It may be useful for noreturn functions, i think:

struct noreturn {
~noreturn() {
if(!uncaught_exception())
throw logical_error("dude, why do you return?");
}
};

void myexit() {
noreturn n;
/* magic syscall sex */

}
 
R

red floyd

    [...]
Does it matter when there is so wide consensus that dtors
shall not throw?
There is wide consensus that destructors usually should not
throw.  There is even wider consensus that every rule may have
exceptions, and I have at least one class whose destructor
always throws.

Sounds like a hack. Can you explain why it always throws?

I once saw something like the following nonignorable:

template<typename T>
class nonignorable {
public:
nonignorable(const T& t) : was_read(false), val(t) { }
operator T() const { was_read = true; return val; }
~nonignorable() {
if (!was_read)
throw logic_error("you ignored me!");
}
private:
bool was_read;
T val;
};

The idea was to ensure that a function return value was used,
or explicitly ignored, e.g.:

nonignorable<int> somefunc()
{
// do stuff
return some_integer;
}
 
R

red floyd

template<typename T>
class nonignorable {
    public:
        nonignorable(const T& t) : was_read(false), val(t) { }
nonignorable(nonignorable<T>& t) : was_read(false),
val(t.val)
{
t.was_read = true;
}
nonignorable& operator=(nonignorable<T>& t)
{
t.was_read = true;
val = t.val;
return *this;
}

// this should probably be an explicit "get()" op
 
J

James Kanze

James said:
"Johannes Schaub (litb)"
[...]
Does it matter when there is so wide consensus that dtors
shall not throw?
There is wide consensus that destructors usually should not
throw. There is even wider consensus that every rule may have
exceptions, and I have at least one class whose destructor
always throws.
Sounds like a hack. Can you explain why it always throws?

Because that's its defined semantics.

The class in question is used to support complex error messages
in exceptions. So you write something like:
error() << "xyz = " << xyz;
The function error returns an instance of the class, which
collects the output in a ostringstream, and throws the exception
with the generated output in its destructor. (It's actually
a bit more complicated, since you need support copy for the
return value of error(), and you only throw when the last copy
is destructed. You also have to check for other exceptions in
the destructor, and just let them propagate, without throwing
yours.)
 
J

James Kanze

"Johannes Schaub (litb)"
[...]
Does it matter when there is so wide consensus that dtors
shall not throw?
There is wide consensus that destructors usually should not
throw. There is even wider consensus that every rule may have
exceptions, and I have at least one class whose destructor
always throws.
Sounds like a hack. Can you explain why it always throws?
[/QUOTE]
I once saw something like the following nonignorable:
template<typename T>
class nonignorable {
public:
nonignorable(const T& t) : was_read(false), val(t) { }
operator T() const { was_read = true; return val; }
~nonignorable() {
if (!was_read)
throw logic_error("you ignored me!");
}
private:
bool was_read;
T val;
};
The idea was to ensure that a function return value was used,
or explicitly ignored, e.g.:
nonignorable<int> somefunc()
{
// do stuff
return some_integer;
}

That's usually an assertion in the destructor, and not a thrown
exception. And you might want to check if the destructor is
being called before asserting---an intervening exception might
mean that it's OK not to check the return value.
 
S

Stuart Golodetz

James said:
James said:
"Johannes Schaub (litb)"
[...]
Does it matter when there is so wide consensus that dtors
shall not throw?
There is wide consensus that destructors usually should not
throw. There is even wider consensus that every rule may have
exceptions, and I have at least one class whose destructor
always throws.
Sounds like a hack. Can you explain why it always throws?

Because that's its defined semantics.

The class in question is used to support complex error messages
in exceptions. So you write something like:
error() << "xyz = " << xyz;
The function error returns an instance of the class, which
collects the output in a ostringstream, and throws the exception
with the generated output in its destructor. (It's actually
a bit more complicated, since you need support copy for the
return value of error(), and you only throw when the last copy
is destructed. You also have to check for other exceptions in
the destructor, and just let them propagate, without throwing
yours.)

Is there a good reason (other than terseness) for preferring that to e.g.

throw exception(stringbuilder() << "xyz = " << xyz);

?

Just curious in case there's a trick I'm missing here (wouldn't be
surprised).

Cheers,
Stu
 
J

James Kanze

James said:
James Kanze wrote:
"Johannes Schaub (litb)"
[...]
Does it matter when there is so wide consensus that dtors
shall not throw?
There is wide consensus that destructors usually should not
throw. There is even wider consensus that every rule may have
exceptions, and I have at least one class whose destructor
always throws.
Sounds like a hack. Can you explain why it always throws?
Because that's its defined semantics.
The class in question is used to support complex error messages
in exceptions. So you write something like:
error() << "xyz = " << xyz;
The function error returns an instance of the class, which
collects the output in a ostringstream, and throws the exception
with the generated output in its destructor. (It's actually
a bit more complicated, since you need support copy for the
return value of error(), and you only throw when the last copy
is destructed. You also have to check for other exceptions in
the destructor, and just let them propagate, without throwing
yours.)
Is there a good reason (other than terseness) for preferring that to e.g.
throw exception(stringbuilder() << "xyz = " << xyz);

Coherence with other error handling mechanisms. But you're
right that it's a bit obfuscating: someone reading the code
doesn't realize that code after this line is unreachable,
however, since the "throw" isn't visible. It's probably not
that good of an idea, although I've seen it used.

In my case, it's a bit more complicated. I have a singleton
class, ProgramStatus, which handles error output and the return
code (which should correspond to the worst error seen). For
fatal errors, I allow the programmer to define what happens: the
default is just to call exit, but in most cases, I'll replace
that with a function which throws an int. So if you call:
ProgramStatus::instance().setError( ProgramStatus::fatal )
<< ...
, the destructor will (very indirectly) throw. (When I set this
error handling, obviously, main consists of one big try block,
with a:
catch ( int returnCode ) {
return returnCode;
}
at the end. This has the advantage, compared to exit, that
destructors of local variables are called.)
 
S

Stuart Golodetz

James said:
James said:
James Kanze wrote:
"Johannes Schaub (litb)"
[...]
Does it matter when there is so wide consensus that dtors
shall not throw?
There is wide consensus that destructors usually should not
throw. There is even wider consensus that every rule may have
exceptions, and I have at least one class whose destructor
always throws.
Sounds like a hack. Can you explain why it always throws?
Because that's its defined semantics.
The class in question is used to support complex error messages
in exceptions. So you write something like:
error() << "xyz = " << xyz;
The function error returns an instance of the class, which
collects the output in a ostringstream, and throws the exception
with the generated output in its destructor. (It's actually
a bit more complicated, since you need support copy for the
return value of error(), and you only throw when the last copy
is destructed. You also have to check for other exceptions in
the destructor, and just let them propagate, without throwing
yours.)
Is there a good reason (other than terseness) for preferring that to e.g.
throw exception(stringbuilder() << "xyz = " << xyz);

Coherence with other error handling mechanisms. But you're
right that it's a bit obfuscating: someone reading the code
doesn't realize that code after this line is unreachable,
however, since the "throw" isn't visible. It's probably not
that good of an idea, although I've seen it used.

I guess it is mildly obfuscating, yes :) I wasn't actually trying to
make a point, though, I was just curious in case there was some reason
to prefer it over what I do at the moment.
In my case, it's a bit more complicated. I have a singleton
class, ProgramStatus, which handles error output and the return
code (which should correspond to the worst error seen). For
fatal errors, I allow the programmer to define what happens: the
default is just to call exit, but in most cases, I'll replace
that with a function which throws an int. So if you call:
ProgramStatus::instance().setError( ProgramStatus::fatal )
<< ...
, the destructor will (very indirectly) throw. (When I set this
error handling, obviously, main consists of one big try block,
with a:
catch ( int returnCode ) {
return returnCode;
}
at the end. This has the advantage, compared to exit, that
destructors of local variables are called.)

Sounds quite cunning :) So are you essentially doing something like
this (ignoring any minor syntax problems -- I'm just typing this out
rather than actually trying it)?

(I'm aware that I've probably messed something up in there, just
wondering whether that's the general gist? :))

struct Error
{
virtual ~Error() {}
virtual void throw_me(const std::string& msg) = 0;
};

struct FatalError : Error
{
void throw_me(const std::string& msg)
{
// etc.
}
};

class ProgramStatus
{
//...

shared_ptr<Error> err_;
shared_ptr<FatalError> fatal_; // initialized elsewhere

Error& err()
{
assert(err_.get());
return *err_;
}

ThrowingStream setError(const shared_ptr<Error>& err)
{
err_ = err;
return ThrowingStream();
}
};

class ThrowingStream
{
//...

std::eek:stringstream os_;

template <typename T>
ThrowingStream& operator<<(const T& t)
{
os_ << t;
return *this;
}

~ThrowingStream()
{
if(!uncaught_exception())
ProgramStatus::instance().err().throw_me(os_.str());
}
};

Cheers,
Stu
 
J

James Kanze

James said:
James Kanze wrote:
James Kanze wrote:
"Johannes Schaub (litb)"
[...]
In my case, it's a bit more complicated. I have a singleton
class, ProgramStatus, which handles error output and the return
code (which should correspond to the worst error seen). For
fatal errors, I allow the programmer to define what happens: the
default is just to call exit, but in most cases, I'll replace
that with a function which throws an int. So if you call:
ProgramStatus::instance().setError( ProgramStatus::fatal )
<< ...
, the destructor will (very indirectly) throw. (When I set this
error handling, obviously, main consists of one big try block,
with a:
catch ( int returnCode ) {
return returnCode;
}
at the end. This has the advantage, compared to exit, that
destructors of local variables are called.)
Sounds quite cunning :) So are you essentially doing something like
this (ignoring any minor syntax problems -- I'm just typing this out
rather than actually trying it)?

It's much, much simpler: no fancy classes are involved, just an
additional variable in the error message collector. Basically,
when I call ProgramStatus::setError(gravity), I return a message
collector which memorizes the gravity, and in its final
destructor, acts on it: if the gravity is "fatal", it calls
ProgramStatus::terminate( returnCode ), which in turn calls
whatever function the client has registered (exit( returnCode
) by default). In most of my programs, I register a function
which just does "throw returnCode". (Throwing an *int*. It's
the only case I've found where it seems appropriate to throw
anything but something derived from std::exception.)
(I'm aware that I've probably messed something up in there, just
wondering whether that's the general gist? :))
struct Error
{
virtual ~Error() {}
virtual void throw_me(const std::string& msg) = 0;

You mean to provide an implementation which does nothing here,
no? Rather than making it pure virtual?
};

struct FatalError : Error
{
void throw_me(const std::string& msg)
{
// etc.
}
};
class ProgramStatus
{
//...
shared_ptr<Error> err_;
shared_ptr<FatalError> fatal_; // initialized elsewhere

Just curious, but why the shared_ptr? What's wrong with just
making them static.
Error& err()
{
assert(err_.get());
return *err_;
}
ThrowingStream setError(const shared_ptr<Error>& err)
{
err_ = err;
return ThrowingStream();
}
};
class ThrowingStream
{
//...
std::eek:stringstream os_;
template <typename T>
ThrowingStream& operator<<(const T& t)
{
os_ << t;
return *this;
}
~ThrowingStream()
{
if(!uncaught_exception())
ProgramStatus::instance().err().throw_me(os_.str());

This is probably the hard part. About the only exception you
might get here is std::bad_alloc. Which you probably want to
replace whatever exception you're generating, because it is more
critical.
 
S

Stuart Golodetz

James said:
James said:
James Kanze wrote:
James Kanze wrote:
"Johannes Schaub (litb)"
[...]
In my case, it's a bit more complicated. I have a singleton
class, ProgramStatus, which handles error output and the return
code (which should correspond to the worst error seen). For
fatal errors, I allow the programmer to define what happens: the
default is just to call exit, but in most cases, I'll replace
that with a function which throws an int. So if you call:
ProgramStatus::instance().setError( ProgramStatus::fatal )
<< ...
, the destructor will (very indirectly) throw. (When I set this
error handling, obviously, main consists of one big try block,
with a:
catch ( int returnCode ) {
return returnCode;
}
at the end. This has the advantage, compared to exit, that
destructors of local variables are called.)
Sounds quite cunning :) So are you essentially doing something like
this (ignoring any minor syntax problems -- I'm just typing this out
rather than actually trying it)?

It's much, much simpler: no fancy classes are involved, just an
additional variable in the error message collector. Basically,
when I call ProgramStatus::setError(gravity), I return a message
collector which memorizes the gravity, and in its final
destructor, acts on it: if the gravity is "fatal", it calls
ProgramStatus::terminate( returnCode ), which in turn calls
whatever function the client has registered (exit( returnCode
) by default). In most of my programs, I register a function
which just does "throw returnCode". (Throwing an *int*. It's
the only case I've found where it seems appropriate to throw
anything but something derived from std::exception.)
(I'm aware that I've probably messed something up in there, just
wondering whether that's the general gist? :))
struct Error
{
virtual ~Error() {}
virtual void throw_me(const std::string& msg) = 0;

You mean to provide an implementation which does nothing here,
no? Rather than making it pure virtual?

Possibly -- it's not a very well-thought-through design to be honest, I
was just trying to hack out the idea.
Just curious, but why the shared_ptr? What's wrong with just
making them static.

fatal_ should indeed be static -- I wasn't thinking straight :) And
err_ could be static too I suppose.

No reason for using shared_ptr; I guess it's bad that that was the first
solution that sprang to mind(?)

I guess I could (should?) have just used e.g.

Error *err_;
static FatalError fatal_;
// etc.

Oh well :) That'll teach me to post half-baked pseudo-code I guess...

Cheers,
Stu
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top