Let’s say we have shared library with the following very simple Type.h
:
struct Base
{
virtual ~Base() = default;
};
struct Derived final : Base
{
Derived();
};
, Type.cpp
:
#include "Type.h"
Derived::Derived() = default;
and main.cpp
program:
#include "Type.h"
int main()
{
std::shared_ptr<Base> pr;
pr = std::make_shared<Derived>();
auto dyncast = dynamic_cast<Derived *>(pr.get());
if (dyncast) {
std::cout << "OKn";
} else {
std::cout << "!!! FAILn";
}
return 0;
}
Compiled with -O2
flag:
> clang++ -dynamiclib -L. -std=gnu++20 -O2 -o libOut.dylib Type.cpp
> clang++ -o out main.cpp -L. -lOut -O2 -std=gnu++20
> ./out
For some reason, with XCode 16 the program fails to dynamic_cast
and outputs fail line.
The following "fixes" the issue:
- removing
final
fromDerived
declaration - inlining
Derived
constructor - adding other
virtual
member function toDerived
- building without
-O2
- downgrading XCode to v.15
As of my understanding, some sort of optimization takes place here, causing dynamic_cast
to fail, but, IMO, this looks like bug, and a huge one. Am I missing something?
Is there some sort of workaround/compiler flag to make this work?
Thank you.
2
Answers
Problem is that virtual desturctors are implicitly defined in header.
This leads to situation where each library/executable has own virtual table for given type. As a result there is problem with RTTI and
dynamic_cast
fails.I’ve reproduced your issue on my MacOS machine and after introducing fixes below it works as it should:
Type.h
Type.cpp
In this form you will have warranty there is only one virtual table for each type and it is inside of
libOut.dylib
You are right that this is an optimisation which breaks in this case. Passing the compiler flag
-fno-assume-unique-vtables
will disable it.