2

This article from boost: Error and Exception Handling puts forth the following program code:

#include <iostream>
struct my_exc1 : std::exception {
  char const* what() const throw();
};
struct my_exc2 : std::exception {
  char const* what() const throw();
};
struct your_exc3 : my_exc1, my_exc2 {};

int main() {
  try {
    throw your_exc3();
  } catch(std::exception const& e) {}
  catch(...) {
    std::cout << "whoops!" << std::endl;
  }
}

When compiling with g++ (GCC) 5.2.0, I get the following

> g++ -std=c++11 custom_exception.cpp
/tmp/ccmbzPOk.o: In function `my_exc1::my_exc1()':
custom_exception.cpp:(.text._ZN7my_exc1C2Ev[_ZN7my_exc1C5Ev]+0x19): undefined reference to `vtable for my_exc1'
/tmp/ccmbzPOk.o: In function `my_exc1::~my_exc1()':
custom_exception.cpp:(.text._ZN7my_exc1D2Ev[_ZN7my_exc1D5Ev]+0xd): undefined reference to `vtable for my_exc1'
/tmp/ccmbzPOk.o: In function `my_exc2::my_exc2()':
custom_exception.cpp:(.text._ZN7my_exc2C2Ev[_ZN7my_exc2C5Ev]+0x19): undefined reference to `vtable for my_exc2'
/tmp/ccmbzPOk.o: In function `my_exc2::~my_exc2()':
custom_exception.cpp:(.text._ZN7my_exc2D2Ev[_ZN7my_exc2D5Ev]+0xd): undefined reference to `vtable for my_exc2'
/tmp/ccmbzPOk.o:(.rodata._ZTV9your_exc3[_ZTV9your_exc3]+0x20): undefined reference to `my_exc1::what() const'
/tmp/ccmbzPOk.o:(.rodata._ZTV9your_exc3[_ZTV9your_exc3]+0x48): undefined reference to `my_exc2::what() const'
/tmp/ccmbzPOk.o:(.rodata._ZTI9your_exc3[_ZTI9your_exc3]+0x18): undefined reference to `typeinfo for my_exc1'
/tmp/ccmbzPOk.o:(.rodata._ZTI9your_exc3[_ZTI9your_exc3]+0x28): undefined reference to `typeinfo for my_exc2'
collect2: error: ld returned 1 exit status

I have seen the identical technique used elsewhere, suggesting to me that this should compile (and link) silently. (As an example, I cite Anthony Williams C++ Concurrency in Action p. 45 where he inherits from std::exception to make empty_stack for the thread-safe stack example.)

I have tried to #include <exception> and despite the fact that this isn't a C++ library problem, I even tried the -lstdc++ flag on the advice of people with similar problems---out of desperation.

I understand that in std::exception, what() is virtual, meaning I should define it---so I'm not sure why it should compile in the first place, but I'm frustrated that it apparently does for other people.

My questions are two: (1) What is the problem, and why does it work for others? (2, conditionally) New to C++, I should also ask what is a good way to implement what() (assuming I will have to) in the most minimal way, since I don't actually want to pass a string with my exception. I don't need to inherit from deeper in the hierarchy, such as std::runtime_error.

1 Answer 1

1

According to C++14 (N3936) [basic.def.odr]/3:

A virtual member function is odr-used if it is not pure.

So my_exc1::what() and my_exc2::what() are odr-used, even though they are never called. Then we have [basic.def.odr]/4:

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required.

So this whole program has undefined behaviour, but the compiler/linker is not required to diagnose it.

The rationale for this lax requirement is to make a linker's job easier: if the linker happens to be able link without including a call to this function or whatever, then it can do so; the C++ standard does not require the linker to do some sort of whole program analysis to determine whether all odr-used functions have bodies.


So this code is bugged and it should have bodies for both of those functions. It also should have #include <exception>. For the people who compiled and executed this code; their iostream included exception (which is permitted but not required), and their linker manifested the undefined behaviour as appearing to link correctly.


To provide a body it is as simple as:

char const *what() const throw() { return ""; }

(assuming you're fine doing it inline). Of course you could return some other fixed string such as "my_exc1". Note that if you only want to return "" then you do not need to re-declare what() at all.

Sign up to request clarification or add additional context in comments.

8 Comments

Re: your "Note that" sentence, I'm just a bit blown away by the idea that code reliant on undefined behaviour made it into these two rather well reputed sources; particularly the Williams book. I'm not saying your wrong! But if you are right there is a lot to be upset about. RE: your proposed what(), It does work, but I don't like the idea of constructing a string (or doing anything that may throw and exception) in an exception. Should I worry about that?
@Timtro "" is a literal of type const char[1], it has static storage duration and does not require any runtime construction, and cannot throw. This is why (I presume) what() returns a pointer, and not a std::string.
I didn't know that virtual functions were automatically odr-used until now, the language is very complicated and experts overlook things regularly! That's why collaboration is so valuable.
Also, NB: In ISO/IEC 14882:2011, I found your quote from [basic.def.odr] in [basic.def.odr]/2 and not /3. Perhaps we are each reading from different versions of the standard.
I just looked in the exception header file, and unless I'm mistaken, isn't what() a pure virtual function? It has no definition, but appears as an abstract specification in the class.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.