Явное создание шаблона шаблона и dynamic_cast в общей библиотеке
Сегодня я наткнулся на проблему, которую я не могу решить. Я компилирую общую библиотеку, которая включает шаблонный класс (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*
?
Ответы
Ответ 1
Я предполагаю, что вы на Linux/GCC, потому что в Windows он должен "просто работать".
Он не "работает" с GCC, потому что для обеспечения поддержки RTTI в GCC используется сравнение указателей. Все это объясняется в этом часто задаваемом GCC, в том числе о том, как его можно решить. EDIT: хотя в этом FAQ говорится, что он не работает с dlopen()
, в то время как явная связь с общей библиотекой должна работать; так что, возможно, есть что-то еще, например, ошибка, упомянутая ниже.
Некоторые другие ссылки, которые я нашел, могут помочь:
dynamic_cast интерфейс из общей библиотеки, который был загружен lt_dlopen (libtool), не работает
динамическое преобразование с интерфейсами
Ошибка С++ dynamic_cast в Mac OS 10.6 Snow Leopard
Ответ 2
Здесь нет ничего удивительного. Даже для обычных не-шаблонных классов вы никогда не должны ожидать, что RTTI будет работать через границы разделяемой библиотеки. Для некоторых компиляторов, на некоторых операционных системах, с некоторыми параметрами компилятора или компоновщика, он может работать, но в целом он не будет и не требуется (явно не указан в стандарте). И даже если вы заработаете, в долгосрочной перспективе это будет неустойчиво.
По моему опыту, случаи, когда RTTI не могут пересекать границы разделяемой библиотеки, намного перевешивают случаи, когда это возможно.
Решение состоит в следующем:
-
Ограничьте все конструкции объектов из этих производных типов в рамках кода разделяемой библиотеки, где используется dynamic_cast (это решение довольно сложно управлять).
-
Не используйте dynamic_cast вообще (это решение идеалистично, редко применимо).
-
Не используйте общие библиотеки (оценивайте, действительно ли разделяемые библиотеки - это то, что вам нужно, или, возможно, выставляете интерфейс более высокого уровня из вашей общей библиотеки, который не отображает полиморфные типы, которые должны быть получены ( которые, похоже, предполагают, что "открытая архитектура" более подходит в вашем приложении)).
-
Определите свою собственную систему RTTI и оператора литья (это может быть сложно, в зависимости от вашего навыка, но это не значит, что много кода, и многие проекты с основным потоком используют это решение, и вы можете найти множество примеры того, как это сделать).