10

我今天偶然发现了一个我似乎无法解决的问题。我正在编译一个共享库,其中包括一个模板类(Derived<T>,其基础是Base)和该类的一些显式实例化。我希望图书馆用户从这个模板类扩展。当我尝试dynamic_cast将用户的实例从Base*to 转移到Derived<T>*.

我已将问题缩小到这个 MWE:

共享库包含以下文件:

基数.h

#ifndef BASE_H_
#define BASE_H_

class Base {
public:
    Base();
    virtual ~Base();
};

#endif /* BASE_H_ */

派生的.h

#ifndef DERIVED_H_
#define DERIVED_H_    
#include <Base.h>

template <typename T>
class Derived : public Base {
public:  
    Derived();
    virtual ~Derived();
};

#endif /* DERIVED_H_ */

派生的.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>;

助手.h

#ifndef HELPER_H_
#define HELPER_H_

#include <Base.h>

class Helper {
public:
    Helper(Base* m);
    virtual ~Helper();

};

#endif /* HELPER_H_ */

助手.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() {
}

使用该库的简单代码可能是:

测试.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>*从 a中获取强制转换Base*

4

2 回答 2

4

我假设您使用的是 Linux/GCC,因为在 Windows 上它应该“正常工作”。

它不能与 GCC “一起工作”,因为 GCC 中的 RTTI 支持依赖于指针比较。这一切都在这个 GCC FAQ中进行了解释,包括如何解决它。编辑:虽然,这个常见问题解答说它不适用,dlopen()而与共享库的显式链接应该有效;所以也许还有别的东西,比如下面提到的错误。

我发现其他一些可能有帮助的链接:
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

于 2011-05-24T15:06:00.760 回答
4

这里没有惊喜。即使对于普通的非模板类,您也不应该期望RTTI 可以跨共享库边界工作。对于某些编译器,在某些操作系统上,带有一些编译器或链接器选项,它可能会工作,但一般来说,它不会,也不需要(在标准中明确未指定)。即使你让它发挥作用,从长远来看也是不可持续的。

以我的经验,RTTI 不能在共享库边界之间交叉的情况远远超过它可以跨越的情况。

解决方案是:

  • 将来自这些派生类型的所有对象构造限制在使用 dynamic_cast 的共享库代码中(这种解决方案很难管理)。

  • 根本不要使用 dynamic_cast (这个解决方案是理想主义的,很少适用)。

  • 不要使用共享库(评估共享库是否真的是您需要的,或者可能从您的共享库中公开一个更高级别的接口,该接口不会公开要派生的多态类型(这似乎表明“开放式架构”更适合您的应用程序))。

  • 定义您自己的 RTTI 系统和铸造操作员(这可能很难,取决于您的技能,但代码量不大,许多主流项目都使用此解决方案,您可以找到大量示例说明如何执行此操作)。

于 2011-05-24T16:53:10.050 回答