I have a C++ framework being built in the latest version of Xcode (9.4.1 at the time of writing) that I am using from Objective-C++ code, again within Xcode. I need to perform a dynamic_cast
from one pointer type to another. However, the dynamic_cast
is only working from a Debug build and is not working from a Release build. Is there something I'm missing or understanding about how dynamic_cast
works here within Objective-C++ that makes this sample fail?
C++ Framework
TestClass.hpp
class Parent {
public:
// https://stackoverflow.com/a/8470002/3938401
// 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++ Command Line App
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;
}
In both Debug and Release, I expect this code to print "True". However, in reality, Release mode prints "False". As a smoke test, the dynamic_cast
at this SO post works just fine.
Interestingly, the same sort of code works from a C++ command line application, again within Xcode. I have tried disabling the optimizer in Release mode, but this did not seem to fix the problem.
I have a sample project up on GitHub here. Remember to compile it in Release to see the reason for my question. I've included the TestCast
scheme for Objective-C++, and the TestCastCPP
scheme for the straight C++.
If the dynamic_cast operator succeeds, it returns a pointer that points to the object denoted by arg . If dynamic_cast fails, it returns 0 . You may perform downcasts with the dynamic_cast operator only on polymorphic classes.
If the cast is successful, dynamic_cast returns a value of type new-type. If the cast fails and new-type is a pointer type, it returns a null pointer of that type. If the cast fails and new-type is a reference type, it throws an exception that matches a handler of type std::bad_cast.
static_cast − This is used for the normal/ordinary type conversion. This is also the cast responsible for implicit type coersion and can also be called explicitly. You should use it in cases like converting float to int, char to int, etc. dynamic_cast −This cast is used for handling polymorphism.
While typeid + static_cast is faster than dynamic_cast , not having to switch on the runtime type of the object is faster than any of them. Save this answer.
It is hard to know the compiler specifics, since how a compiler does RTTI has some flexibility (i.e., not specified in great detail by the specification).
In this case, since Child class did not have any virtual functions defined, I suspect the compiler had emitted RTTI with every translation unit for the Child class.
When the framework was linked, and when the executable was linked, each had their own Child RTTI information, since each translation unit emitted its own RTTI.
The Parent link of the one did not match the Parent link of the other, I suspect, so they did not have the same Parent pointer and those things were not "fixed up" by the dynamic loader. (The dynamic_cast<Child*>
basically walks the parent pointer chain until it finds a match by pointer value, not by RTTI value.)
If you looked at nm -g TestCast | c++filt
dumps of the app and of the framework, you can see the RTTI blocks. Disassembling them, I think the Child RTTI was already resolved in both situations to their own Parent RTTI.
Why did it work for DEBUG, but not RELEASE? Probably, one of the release optimizations was dead-code stripping of external linkage symbols based on usage. So the dynamic loader (dyld) for DEBUG was able to resolve the symbols, but the RELEASE build one-or-more symbols was already internally resolved.
There's probably a way to indicate an "unused" symbol for the RTTI should be retained and exported, which will vary by compiler/linker. But that's more bother than providing an explicit "first virtual function" (such as the virtual Child destructor) which avoids the problem.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With