Reputation: 193
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 << "OK\n";
} else {
std::cout << "!!! FAIL\n";
}
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:
final
from Derived
declarationDerived
constructorvirtual
member function to Derived
-O2
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.
Upvotes: 4
Views: 217
Reputation: 205
You are right that this is an optimisation which breaks in this case. Passing the compiler flag -fno-assume-unique-vtables
will disable it.
Upvotes: 0
Reputation: 38112
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
#pragma once
struct Base
{
virtual ~Base();
};
struct Derived final : Base
{
Derived();
~Derived();
};
Type.cpp
#include "Type.h"
Base::~Base() = default;
Derived::Derived() = default;
Derived::~Derived() = default;
In this form you will have warranty there is only one virtual table for each type and it is inside of libOut.dylib
Upvotes: 1