Неопределенная ссылка на статический const int
я столкнулся с интересной проблемой сегодня. Рассмотрим этот простой пример:
template <typename T>
void foo(const T & a) { /* code */ }
// This would also fail
// void foo(const int & a) { /* code */ }
class Bar
{
public:
static const int kConst = 1;
void func()
{
foo(kConst); // This is the important line
}
};
int main()
{
Bar b;
b.func();
}
при компиляции я получаю ошибку:
Undefined reference to 'Bar::kConst'
теперь я почти уверен, что это потому, что static const int
нигде не определено, что является преднамеренным, потому что, согласно моему пониманию, компилятор должен быть в состоянии сделать замену во время компиляции и не нуждаться в определении. Однако, поскольку функция принимает const int &
параметр, кажется, не делает подстановка, а вместо этого предпочитая ссылку. Я могу решить эту проблему, сделав следующее изменение:
foo(static_cast<int>(kConst));
Я считаю, что теперь это заставляет компилятор сделать временный int, а затем передать ссылку на то, что он может успешно сделать во время компиляции.
мне было интересно, было ли это намеренно, или я ожидаю слишком многого от gcc, чтобы справиться с этим делом? Или это то, что я не должен делать по какой-то причине?
8 ответов:
это намеренно, 9.4.2/4 говорит:
если статический элемент данных имеет тип const integral или const enumeration, его объявление в классе определение может указать константа-инициализатор, который должен быть интегральное константное выражение (5.19) в в этом случае член может появиться в интегральные константные выражения. Этот член все еще определяются область пространства имен, если она используется в программа
при передаче статических данных член по ссылке const, вы "используете" его, 3.2 / 2:
выражение потенциально вычисляется если только он не появляется там, где Интеграл требуется постоянное выражение (см. 5.19), является операндом оператора sizeof (5.3.3), или является операндом оператора sizeof оператор typeid и выражение не обозначает значение lvalue полиморфный тип класса (5.2.8). Один объект или ненагруженная функция используется, если его имя появляется в потенциально-оценочный выражение.
так что на самом деле, вы "используете" его, когда вы передаете его по значению тоже, или в
static_cast
. Просто GCC позволил вам сорваться с крючка в одном случае, но не в другом.[Edit: gcc применяет правила из черновиков C++0x: "переменная или ненагруженная функция, имя которой отображается как потенциально оцененное выражение, используется odr, если только это не объект, удовлетворяющий требованиям для отображения в константном выражении (5.19) и lvalue-to-rvalue преобразования (4.1) применяется немедленно.". Статическое приведение выполняет преобразование lvalue-rvalue немедленно, поэтому в C++0x оно не "используется".]
практическая проблема с ссылкой на const заключается в том, что
foo
имеет право взять адрес своего аргумента и сравнить его, например, с адресом аргумента из другого вызова, хранящегося в глобальном. Поскольку статический элемент данных является уникальным объектом, это означает, что если вы вызываетеfoo(kConst)
из двух разных TUs, затем адрес переданный объект должен быть одинаковым в каждом случае. AFAIK GCC не может организовать это, если объект не определен в одном (и только одном) TU.хорошо, так что в этом случае
foo
является шаблоном, поэтому определение видно во всех TUs, поэтому, возможно, компилятор теоретически может исключить риск того, что он что-либо сделает с адресом. Но в целом вы, конечно, не должны принимать адреса или ссылки на несуществующие объекты; -)
Если вы пишете статическую переменную const с инициализатором внутри объявления класса, это похоже на то, как если бы вы написали
class Bar { enum { kConst = 1 }; }
и GCC будет относиться к нему так же, что означает, что у него нет адреса.
правильный код должен быть
class Bar { static const int kConst; } const int Bar::kConst = 1;
Это действительно веские аргументы. Тем более, что фу может быть функция из STL, как std:: count что происходит const T& в качестве третьего аргумента.
Я потратил много времени, пытаясь понять, почему у компоновщика были проблемы с таким базовым кодом.
сообщение об ошибке
неопределенная ссылка на 'Bar:: kConst'
говорит нам, что компоновщик не может найти символ.
$nm -C main.o 0000000000000000 T main 0000000000000000 W void foo<int>(int const&) 0000000000000000 W Bar::func() 0000000000000000 U Bar::kConst
мы можем видеть из 'U', что Bar:: kConst не определен. Следовательно, когда компоновщик пытается выполнить свою работу, он должен найти символ. Но ты только объявить kConst и не определяют его.
решение в C++ также определить его следующим образом:
template <typename T> void foo(const T & a) { /* code */ } class Bar { public: static const int kConst = 1; void func() { foo(kConst); // This is the important line } }; const int Bar::kConst; // Definition <--FIX int main() { Bar b; b.func(); }
затем вы можете видеть, что компилятор поместит определение в сгенерированный объектный файл:
$nm -C main.o 0000000000000000 T main 0000000000000000 W void foo<int>(int const&) 0000000000000000 W Bar::func() 0000000000000000 R Bar::kConst
Теперь вы можете видеть, что " R " говорит, что он определен в раздел данных.
г++ версии 4.3.4 принимает этот код (см. этой ссылке). Но G++ версии 4.4.0 отвергает его.
Я думаю, что этот артефакт C++ означает, что в любое время, что
Bar::kConst
упоминается, вместо него используется его литеральное значение.Это означает, что на практике нет никакой переменной, чтобы сделать опорную точку.
возможно, вам придется сделать это:
void func() { int k = kConst; foo(k); }
простой трюк: используйте
+
доkConst
передавались функции. Это предотвратит получение константы из ссылки, и таким образом код не будет генерировать запрос компоновщика к объекту константы, но вместо этого он будет продолжать использовать значение константы времени компилятора.