为什么这个Objective-C++的dynamic_cast在调试模式下成功了,但在发布模式下失败了呢?

7
我正在使用最新版本的Xcode(写作时为9.4.1)构建一个C++框架,并从Objective-C++代码中使用它,同样在Xcode中。我需要执行一个dynamic_cast,将一个指针类型转换为另一个指针类型。然而,在Debug构建中dynamic_cast正常工作,但在Release构建中不起作用。是否有什么我没有注意到或者理解错了,导致这个示例失败了? C++框架 TestClass.hpp
class Parent {
    public:
    // https://dev59.com/_moy5IYBdhLWcg3wkOwK#8470002
    // must have at least 1 virtual function for RTTI
    virtual ~Parent();

    Parent() {}
};

class Child : public Parent {
public:
    // if you put the implementation for this func 
    // in the header, everything works.
    static Child* createRawPtr(); 
};

TestClass.cpp

#include "TestClass.hpp"

Parent::~Parent() {}

Child* Child::createRawPtr() {
    return new Child;
}

Objective-C++ 命令行应用程序

main.mm

#import <Foundation/Foundation.h>
#import <TestCastCPP/TestClass.hpp>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Parent *parentPtr = Child::createRawPtr();
        Child *child = dynamic_cast<Child*>(parentPtr);
        NSLog(@"Was the cast successful? %s", child != nullptr ? "True" : "False");
    }
    return 0;
}

在Debug和Release模式下,我期望这段代码输出“True”,但实际上,在Release模式下会输出“False”。作为一项冒烟测试,dynamic_cast这个SO帖子中可以正常工作。

有趣的是,同样的代码在C++命令行应用程序中也能正常工作,同样是在Xcode中。我尝试在Release模式下禁用优化器,但这似乎并没有解决问题。
我在GitHub上上传了一个示例项目在这里。记得在Release模式下编译它,以查看我的问题原因。我包括了Objective-C++的TestCast方案和直接C++的TestCastCPP方案。

1
如果您提供一个显式的虚拟子类析构函数,并将其定义放在父类析构函数之后,是否仍然存在相同的问题?(因为 https://dev59.com/_moy5IYBdhLWcg3wkOwK#8470002 也适用于子类。) - Eljay
@Eljay 很好的问题。我尝试了你的建议(在头文件中声明Child析构函数,使用override,实现与Parent析构函数一样),它起作用了!(编辑:我看到了你关于RTTI的编辑。嗯。) - Deadpikle
@Eljay,我在我的工作项目中尝试了一下(这也是问题最初出现的地方),你的解决方案起作用了。RTTI推理很有道理,尽管我不确定为什么它在Objective-C++中不起作用。请将其发布为答案,以便我可以接受它。谢谢! - Deadpikle
1个回答

2
很难了解编译器的具体情况,因为编译器如何进行运行时类型信息(RTTI)有一定的灵活性(即规范没有详细说明)。在这种情况下,由于Child类没有定义任何虚函数,我怀疑编译器已经为每个翻译单元发出了Child类的RTTI。当框架链接时和可执行文件链接时,每个都有自己的Child RTTI信息,因为每个翻译单元都发出了自己的RTTI。我怀疑其中一个的父链接与另一个的父链接不匹配,所以它们没有相同的父指针,并且动态加载程序没有对它们进行"修复"。(dynamic_cast基本上是通过指针值遍历父指针链,直到找到匹配项,而不是通过RTTI值。)如果您查看应用程序和框架的nm -g TestCast | c++filt转储,您可以看到RTTI块。通过反汇编它们,我认为Child RTTI已经在两种情况下分别解析为它们自己的Parent RTTI。为什么DEBUG可以工作,但RELEASE不行?可能是RELEASE优化之一是基于使用情况剥离外部链接符号的死代码。因此,DEBUG的动态加载程序(dyld)能够解析符号,但RELEASE版本中的一个或多个符号已经在内部解析。可能有一种方法可以指示应保留并导出RTTI的"未使用"符号,这将因编译器/链接器而异。但是,这比提供显式的"第一个虚函数"(例如虚Child析构函数)更麻烦,后者避免了这个问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接