【问题标题】:Explicit instantiation of a templated class and dynamic_cast in a shared library共享库中模板类和 dynamic_cast 的显式实例化
【发布时间】:2011-05-24 14:49:43
【问题描述】:

我今天偶然发现了一个我似乎无法解决的问题。我正在编译一个共享库,其中包括一个模板类(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 失败了?

而且,在我想保持像示例一样的类结构,并继续使用共享库的前提下:如何才能从Base* 成功获取Derived&lt;some type&gt;* 转换?

【问题讨论】:

  • 缩小范围的建议... 1) 绝对确保传递给编译器的选项对于您的 lib 组件和您的程序组件是相同的...它们会影响管理符号的形成. 2) 使用“nm”工具从库对象和程序对象中转储符号...您应该看到库组件(已定义)和测试代码(未定义)的相同损坏符号

标签: c++ templates shared-libraries dynamic-cast


【解决方案1】:

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

它不能与 GCC “一起工作”,因为 GCC 中的 RTTI 支持依赖于指针比较。这一切都在this 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

【讨论】:

  • 我在mac下使用gcc,还没试过Linux/gcc。我会看看你的链接。
  • 我昨晚尝试了 Linux/GCC 4.4.5 和 4.5.2,它开箱即用。在 Mac OSX / GCC 4.2.1 上它没有。为了在 Mac 上解决它,我点击了您的链接并使用了 -mmacosx-version-min=10.4 选项。奇怪的是,-Wl,-no_compact_linkedit 标志对我不起作用,但我相信这可能是由 CMake 处理其编译器/链接器标志的方式引起的。我仍在寻找具有 gcc 4.2.1 的 old linux 机器以进行测试。感谢您的帮助
【解决方案2】:

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

根据我的经验,RTTI 无法在共享库边界之间进行交叉的情况远远超过了可以的情况。

解决办法是:

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

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

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

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

【讨论】:

  • 您能举一些定义自己的RTTI系统的主流项目的例子吗?
  • LLVM、Boost.Serialization、Qt 和 VCL/CLX 等大多数 GUI 工具、DynObj 以及我自己的项目(这不是主流)。
  • 谢谢你,米凯尔。我正在认真考虑最后一个选项……我得先检查我的枕头几个晚上。
  • @MikaelPersson:这仅适用于template 类还是适用于共享库中定义的任何类?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-11
  • 1970-01-01
  • 1970-01-01
  • 2013-02-09
  • 2021-11-25
相关资源
最近更新 更多