我今天偶然发现了一个问题,似乎无法解决。我正在编译一个包含模板化类(Derived<T>
,其基数为Base
)的共享库以及该类的一些显式实例化。我希望库用户从这个模板化的类扩展。当我尝试从dynamic_cast
到Base*
Derived<T>*
用户的实例时出现问题。
我已将问题缩小到这个MWE:
共享库包含以下文件:
Base.h
#ifndef BASE_H_
#define BASE_H_
class Base {
public:
Base();
virtual ~Base();
};
#endif /* BASE_H_ */
Derived.h
#ifndef DERIVED_H_
#define DERIVED_H_
#include <Base.h>
template <typename T>
class Derived : public Base {
public:
Derived();
virtual ~Derived();
};
#endif /* DERIVED_H_ */
Derived.cpp
#include <Derived.h>
template <typename T>
Derived<T>::Derived() :
Base() {
}
template <typename T>
Derived<T>::~Derived() {
}
// explicit instantiations
template class Derived<float>;
template class Derived<double>;
template class Derived<long double>;
Helper.h
#ifndef HELPER_H_
#define HELPER_H_
#include <Base.h>
class Helper {
public:
Helper(Base* m);
virtual ~Helper();
};
#endif /* HELPER_H_ */
Helper.cpp
#include <Helper.h>
#include <Base.h>
#include <Derived.h>
#include <iostream>
using namespace std;
Helper::Helper(Base* m) {
cout << "after received " << m << endl;
cout << "after fom: " << dynamic_cast< Derived<float>* >(m) << endl;
cout << "after dom: " << dynamic_cast< Derived<double>* >(m) << endl;
cout << "after ldom: " << dynamic_cast< Derived<long double>* >(m) << endl;
cout << "===" << endl;
}
Helper::~Helper() {
}
使用该库的简单代码可能是:
TEST.CPP
#include <Derived.h>
#include <Helper.h>
#include <iostream>
using namespace std;
class MyModel : public Derived<double> {
public:
MyModel() : Derived<double>() {
};
virtual ~MyModel() {
};
};
int main(int argc, char *argv[]) {
MyModel om1;
cout << "created mymodel " << &om1 << endl;
cout << "before fom: " << dynamic_cast< Derived<float>* >(&om1) << endl;
cout << "before dom: " << dynamic_cast< Derived<double>* >(&om1) << endl;
cout << "before ldom: " << dynamic_cast< Derived<long double>* >(&om1) << endl;
cout << "===" << endl;
Helper root(&om1);
return 0;
}
问题在于,当我创建共享库并链接test.cpp
时,dynamic_cast
失败。这是一个示例输出:
created mymodel 0x7fff5fbff3e0
before fom: 0
before dom: 0x7fff5fbff3e0
before ldom: 0
===
after received 0x7fff5fbff3e0
after fom: 0
after dom: 0 // <<< Here I expected it to succeed and return a non-null pointer
after ldom: 0
===
但是,如果我一起编译整个库和示例,则转换成功:
created mymodel 0x7fff5fbff3e0
before fom: 0
before dom: 0x7fff5fbff3e0
before ldom: 0
===
after received 0x7fff5fbff3e0
after fom: 0
after dom: 0x7fff5fbff3e0
after ldom: 0
===
我的问题是:为什么dynamic_cast
失败?
并且,在我希望维护类似示例的类结构的前提下,继续使用共享库:如何从Derived<some type>*
成功获取Base*
强制转换?< / p>
答案 0 :(得分:4)
这里没有任何意外。即使对于普通的非模板化类,也不应该期望 RTTI跨共享库边界工作。对于某些编译器,在某些操作系统上,使用某些编译器或链接器选项,它可能会起作用,但一般来说,它不会,也不是必需的(在标准中明确地保留未指定的)。即使你让它发挥作用,从长远来看也是不可持续的。
根据我的经验,RTTI无法在共享库边界之间交叉的情况远远超过可能的情况。
解决方案是:
将来自这些派生类型的对象的所有构造限制在使用dynamic_cast的共享库代码中(此解决方案很难管理)。
根本不要使用dynamic_cast(此解决方案是理想主义的,很少适用)。
不要使用共享库(评估共享库是否真的是您需要的,或者可能是从共享库中公开更高级别的接口,而不会暴露出派生的多态类型(这似乎表明“开放式架构”在您的应用中更合适))。
定义您自己的RTTI系统和强制转换操作符(这可能很难,具体取决于您的技能,但它不会占用太多代码,许多主流项目使用此解决方案,您可以找到大量的如何做到这一点的例子。)
答案 1 :(得分:3)
我假设您使用的是Linux / GCC,因为在Windows上它应该“正常工作”。
它与GCC“不起作用”,因为对于性能,GCC中的RTTI支持依赖于指针比较。这一切都在this GCC FAQ中解释,包括如何解决。编辑:虽然,这个常见问题解答说,它不适用于dlopen()
,而显式链接共享库应该工作;所以也许还有别的东西,比如下面提到的bug。
我发现其他一些链接可以提供帮助:
dynamic_cast an interface from a shared library which was loaded by lt_dlopen(libtool) doesn't work
dynamic cast with interfaces
C++ dynamic_cast bug in Mac OS 10.6 Snow Leopard