Неопределенная ссылка на статический 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 65

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 передавались функции. Это предотвратит получение константы из ссылки, и таким образом код не будет генерировать запрос компоновщика к объекту константы, но вместо этого он будет продолжать использовать значение константы времени компилятора.

вы также можете заменить его функцией-членом constexpr:

class Bar
{
  static constexpr int kConst() { return 1; };
};

Я испытал ту же проблему, что и упомянутый Cloderic (static const в тернарном операторе:r = s ? kConst1 : kConst2), но он только жаловался после выключения оптимизации компилятора (-O0 вместо-Os). Произошло на gcc-none-eabi 4.8.5.