I stumbled into a problem today that I can't seem to solve. I am compiling a shared library that includes a templated class (Derived<T>
, whose base is Base
) and some explicit instantiations of this class. I would like the library user to extend from this templated class. The problem arises when I try to dynamic_cast
the user's instance from Base*
to Derived<T>*
.
I have narrowed down the problem to this MWE:
The shared library contains the following files:
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: " <<开发者_C百科 dynamic_cast< Derived<long double>* >(m) << endl;
cout << "===" << endl;
}
Helper::~Helper() {
}
And a simple code that uses the library could be:
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;
}
The problem is that when I create a shared library and link test.cpp
against it, the dynamic_cast
fails. Here's an example output:
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
===
However, if I compile the whole library and example together, the cast succeeds:
created mymodel 0x7fff5fbff3e0
before fom: 0
before dom: 0x7fff5fbff3e0
before ldom: 0
===
after received 0x7fff5fbff3e0
after fom: 0
after dom: 0x7fff5fbff3e0
after ldom: 0
===
My question is : Why is the dynamic_cast
failing?
And, under the premise that I would like to maintain a class structure like the example, and continue to use a shared library: how can I successfully obtain the Derived<some type>*
cast from a Base*
?
I assume you are on Linux/GCC, because on Windows it should "just work".
It does not "just work" with GCC, because for performance RTTI support in GCC relies on pointer comparison. It is all explained in this GCC FAQ, including how it can be resolved. EDIT: though, this FAQ says that it does not work with dlopen()
while explicit linking with a shared library should work; so maybe there is something else, such as the bug mentioned below.
Some other links I found that can be of help:
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
There is no surprise here. Even for normal non-templated classes, you should never expect the RTTI to work across shared-library boundaries. For some compilers, on some OSes, with some compiler or linker options, it might work, but in general, it will not, and is not required to (explicitly left unspecified in the Standard). And even if you make it work, it will be unsustainable in the long-run.
In my experience, the cases when the RTTI cannot cross-over between shared-library boundaries far outweigh the cases when it can.
The solution is to either:
Restrict all the constructions of objects from these derived types to within the shared-library code where the dynamic_cast is used (this solution is pretty hard to manage).
Do not use dynamic_cast at all (this solution is idealistic, seldom applicable).
Do not use shared-libraries (evaluate if shared-libraries are really what you need, or maybe, expose a higher-level interface from your shared-library, that doesn't expose polymorphic types to be derived (which seem to suggest that an "open-architecture" is more appropriate in your application)).
Define your own RTTI system and casting operator (this could be hard, depending on your skill, but it doesn't amount to much code, and many main-stream projects use this solution and you can find plenty of examples on how to do this).
精彩评论