Search code examples
c++linker-errorscustom-exceptions

Boost example code inheriting from std::exception doesn't link


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.


Solution

  • 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.