skip to Main Content

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 from Derived declaration
  • inlining Derived constructor
  • adding other virtual member function to Derived
  • 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


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

    Login or Signup to reply.
  2. You are right that this is an optimisation which breaks in this case. Passing the compiler flag -fno-assume-unique-vtables will disable it.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search