XCodeでハマったこと
こんにちは、株式会社CFlatです。
XCodeにて、C/C++ Library(Type=Dynamic)と、Command Line Tool(Type=C++)の、2つのプロジェクトを用意します。そしてそれぞれに次のようなコードを書いて、デバッグビルドします。
A.h (project1)
#include <cstdio> #include <string> class Root { public : Root() {} virtual ~Root() {} static void print(const std::string& label, const Root& t) ; } ; class Base : public Root { public : Base() {} virtual ~Base() {} } ; class Derived : public Base { public : Derived() {} virtual ~Derived() {} } ; class Other : public Root { public : Other() {} virtual ~Other() {} } ;
A.cpp (project1)
#include "A.h" void Root::print(const std::string& label, const Root& t) { std::printf("%s*:%p, Base*:%p\n", label.c_str(), &t, dynamic_cast<const Base*>(&t)) ; }
main.cpp (project2)
#include "A.h" int main(int argc, const char * argv[]) { Base::print("Root ", Root()) ; // Root *:0x7fff5fbff808, Base*:0x0 Base::print("Base ", Base()) ; // Base *:0x7fff5fbff7f0, Base*:0x7fff5fbff7f0 Base::print("Derived", Derived()) ; // Derived*:0x7fff5fbff7d8, Base*:0x7fff5fbff7d8 Base::print("Other ", Other()) ; // Other *:0x7fff5fbff7c0, Base*:0x0 return 0; }
Root→Base→Derivedという継承関係があるので、BaseとDerivedは当然ながらBaseにdynamic_castできます。RootのインスタンスとOtherのインスタンスは、Baseではないのでnullptrになります。ここまでは当たり前。
次に、Edit Scheme...→Build ConfigurationをReleaseとして同じコードを実行すると、出力は次のようになります。
Root *:0x7fff5fbff7d0, Base*:0x0
Base *:0x7fff5fbff7b8, Base*:0x0
Derived *:0x7fff5fbff7a0, Base*:0x0
Other *:0x7fff5fbff788, Base*:0x0
なんと唐突に、dynamic_castが全てnullptrを返すようになってしまいます!
実はこの現象、print()メンバ関数をcppではなくヘッダファイル内にinlineで定義した場合には、起こらないのです。
どうやらRelaseビルドでは、ライブラリ側のRTTI(実行時型情報)が正しく取れてこない模様。ダイナミックリンクするライブラリでは、dynamic_castを使えないのでしょうか?
もちろんそんな事はなくて、次のようなコードでクラス定義を挟み込んでやれば、挟まれた部分のクラスのRTTIも、ライブラリ外に公開されてくれます。
#pragma GCC visibility push(default) // ここにクラス定義 #pragma GCC visibility pop
いや、このpragmaの存在自体は知ってたんですが、まさかそんな意味があったとは……。