У меня есть три заголовочных файла в моем проекте которые описывают объекты Rational
, Complex
, а также RubyObject
. Первые два являются шаблонами. Все могут быть преобразованы с помощью конструкторов копирования, которые определены в заголовочных файлах — за исключением тех, которые создают Rational
а также Complex
от const RubyObject&
с, которые определены в исходном файле.
Замечания: Эти определения есть по необходимости. Если они все иди в заголовки, получаешь круговая зависимость.
Некоторое время назад я столкнулся с некоторые нерешенные ошибки символов с двумя конструкторами копирования, определенными в исходном файле. Мне удалось включить в исходный файл следующую функцию
void nm_init_data() {
nm::RubyObject obj(INT2FIX(1));
nm::Rational32 x(obj);
nm::Rational64 y(obj);
nm::Rational128 z(obj);
volatile nm::Complex64 a(obj);
volatile nm::Complex128 b(obj);
}
а потом вызов nm_init_data()
из точки входа библиотеки в основной исходный файл. Это заставило эти символы быть правильно связаны.
К сожалению, я недавно обновил GCC, и ошибки вернулись. На самом деле, похоже, что это происходит в немного другом месте с GCC 4.6 (например, на Travis-CI).
Но это не проблема конкретной версии (как я думал раньше). Мы видим это на Система Travis CI, основанная на Ubuntu, который работает GCC 4.6. Но мы не видим его на машине с Ubuntu с GCC 4.8.1 или 4.8.2. Но мы делать увидеть его на компьютере Mac OS X с 4.8.2 — и не на той же машине с 4.7.2. Отключение оптимизации тоже не помогает.
Если я бегу nm
в моей библиотеке символ определенно не определен:
$ nm tmp/x86_64-darwin13.0.0/nmatrix/2.0.0/nmatrix.bundle |grep RationalIsEC1ERKNS
U __ZN2nm8RationalIsEC1ERKNS_10RubyObjectE
00000000004ca460 D __ZZN2nm8RationalIsEC1ERKNS_10RubyObjectEE18rb_intern_id_cache
00000000004ca458 D __ZZN2nm8RationalIsEC1ERKNS_10RubyObjectEE18rb_intern_id_cache_0
Я не уверен, почему есть две определенные записи, которые подчинены неопределенному символу, но я также не знаю так много, как хотелось бы о компиляторах.
Также похоже, что конструктор копирования является неопределенным символом для каждой версии Rational
шаблон:
__ZN2nm8RationalIiEC1ERKNS_10RubyObjectE
__ZN2nm8RationalIsEC1ERKNS_10RubyObjectE
__ZN2nm8RationalIxEC1ERKNS_10RubyObjectE
«Ну, это странно, — подумал я. «Complex64
а также Complex128
также называются в этом nm_init_data
функции, но они оба разрешают правильно — и не перечислены в nm -u
вывод. «Таким образом, я попытался добавить volatile
до создания Rational copy, думая, что, возможно, компилятор оптимизировал то, что мы не хотим оптимизировать. Но это тоже не исправило, к сожалению. Это сделано с оговоркой:
void nm_init_data() {
volatile VALUE t = INT2FIX(1);
volatile nm::RubyObject obj(t);
volatile nm::Rational32 x(const_cast<nm::RubyObject&>(obj));
volatile nm::Rational64 y(const_cast<nm::RubyObject&>(obj));
volatile nm::Rational128 z(const_cast<nm::RubyObject&>(obj));
volatile nm::Complex64 a(const_cast<nm::RubyObject&>(obj));
volatile nm::Complex128 b(const_cast<nm::RubyObject&>(obj));
}
Предостережение в том, что теперь я получаю точно такую же ошибку, но вместо этого для сложных объектов. Argh!
dyld: lazy symbol binding failed: Symbol not found: __ZN2nm7ComplexIdEC1ERKNS_10RubyObjectE
Referenced from: /Users/jwoods/Projects/nmatrix/lib/nmatrix.bundle
Expected in: flat namespace
dyld: Symbol not found: __ZN2nm7ComplexIdEC1ERKNS_10RubyObjectE
Referenced from: /Users/jwoods/Projects/nmatrix/lib/nmatrix.bundle
Expected in: flat namespace
Это совершенно абсурдно. Вот определения для обеих этих функций в том же исходном файле, что и nm_init_data()
функция:
namespace nm {
template <typename Type>
Complex<Type>::Complex(const RubyObject& other) {
// do some things
}
template <typename Type>
Rational<Type>::Rational(const RubyObject& other) {
// do some other things
}
} // end of namespace nm
Подсказка: Стоит отметить, что ошибка не возникает, когда nm_init_data()
вызывается (т.е. когда библиотека загружена). Это происходит намного позже, во время очередного вызова этих проблемных функций.
Как мне решить эту проблему раз и навсегда, а другим это нравится?
Вы утверждаете следующее, в чем я сомневаюсь.
Эти определения есть по необходимости. Если они все идут в заголовках, вы получаете круговую зависимость.
В большинстве случаев вы можете решить такую круговую запутанность, разделив ваш код на дополнительный файл .hpp, который включен вместе с определением класса, содержащим определения шаблонов, где это необходимо.
Если ваш код имеет реальную циклическую зависимость, он не сможет скомпилироваться. Обычно, если ваши зависимости кажутся циклическими, вам нужно посмотреть поближе и перейти к уровню метода и проверить, какая из них потребует компиляции обоих типов.
Возможно, ваши типы используют друг друга, а затем скомпилируют все в один файл .cpp (например, через три .hpp включений).
Или есть только указатель на другой тип, затем используйте предварительные объявления, чтобы убедиться, что все шаблоны разрешены.
Или, в-третьих, у вас есть какой-то метод, который зависит от прямого, а другой — от обратного, затем поместите один вид в один файл, а другие — в другой, и у вас все в порядке.
Кроме того, кажется, что вы должны использовать предварительную декларацию для ваших пропавших предметов. Я хотел бы ожидать что-то вроде следующего после определения функции. Например.:
template nm::Complex<nm::RubyObject>::Complex(const nm::RubyObject& other);
Rational
,Complex
… шаблоныконструкторы копирования … определены в заголовочных файлах — за исключением тех, которые создают
Rational
а такжеComplex
отconst RubyObject&
s, которые определены в исходном файле.
И в этом ваша проблема. поскольку Rational
а также Complex
шаблоны, все их методы должны быть доступны в вашем заголовочном файле.
Если это не так, то иногда вы можете обойтись без этого в зависимости от порядка, в котором вещи называются, и порядка, в котором они связаны, — но чаще вы будете получать странные ошибки с неопределенными символами, которые это именно то, что здесь происходит.
Просто переместите определения Rational(const RubyObject&)
а также Complex(const RubyObject&)
в соответствующие заголовки и все должно просто работать.