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.

Timtro
  • 418
  • 5
  • 15

1 Answers1

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.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • 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 Aug 16 '15 at 04:05
  • @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`. – M.M Aug 16 '15 at 04:07
  • 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. – M.M Aug 16 '15 at 04:08
  • 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. – Timtro Aug 16 '15 at 04:33
  • 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. – Timtro Aug 16 '15 at 04:41
  • @Timtro `my_exc1::what()` is not pure because it doesn't have `= 0;` in its declaration. But it is virtual because it overrides a virtual function. I don't think `std::exception::what()` is allowed to be pure anyway (see [exception]/9). Also, I'm using N3936 which is very close to C++14. – M.M Aug 16 '15 at 04:46
  • The code you quoted has a copyright 2003 note at the bottom of the page (although I checked old versions of the standard and this same ODR rule has been there since C++98) – M.M Aug 16 '15 at 04:56
  • I think I understand now. Being new to C++, I mistakenly thought that simply not including a definition made it pure. Thanks again. P.S. You're quite right that C++ is a complicated language! Though I love it so far, I do miss the comfort I had with C. I couldn't go back though. – Timtro Aug 16 '15 at 05:22