Неопределенная ссылка на static const int - PullRequest
71 голосов
/ 22 марта 2011

Сегодня я столкнулся с интересной проблемой.Рассмотрим простой пример:

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 ]

60 голосов
/ 22 марта 2011

Это преднамеренно, 9.4.2 / 4 гласит:

Если статический член данных имеет константный интеграл или константный тип перечисления, его объявление в определении класса может указывать инициализатор константы, который долженбыть интегральным константным выражением (5.19). В этом случае член может появляться в виде интегральных константных выражений.Член по-прежнему должен быть определен в области имен, если он используется в программе

Когда вы передаете элемент статических данных по константной ссылке, вы его «используете», 3.2 / 2:

Выражение потенциально оценивается, если только оно не появляется там, где требуется целочисленное константное выражение (см. 5.19), является операндом оператора sizeof (5.3.3) или операндом оператора typeid и выражения.не обозначает lvalue типа полиморфного класса (5.2.8).Объект или не перегруженная функция используется, если его имя появляется в потенциально вычисляемом выражении.

Таким образом, фактически, вы «используете» его, когда передаете его также по значению или в static_cast.Просто GCC позволил вам снять трубку в одном случае, но не в другом.

[Редактировать: gcc применяет правила из черновиков C ++ 0x: «Переменная или не перегруженная функция, имя которой появляетсяпоскольку потенциально оцениваемое выражение используется в odr, если только оно не является объектом, удовлетворяющим требованиям для появления в константном выражении (5.19), и немедленно применяется преобразование lvalue-to-rvalue (4.1). ".Статическое приведение немедленно выполняет преобразование lvalue-rvalue, поэтому в C ++ 0x оно не «используется».]

Практическая проблема со ссылкой на const заключается в том, что foo имеет право принимать адресего аргумент, и сравните его, например, с адресом аргумента из другого вызова, хранящегося в глобальном.Поскольку член статических данных является уникальным объектом, это означает, что если вы вызываете foo(kConst) из двух разных TU, то адрес передаваемого объекта должен быть одинаковым в каждом случае.AFAIK GCC не может организовать это, если объект не определен в одном (и только одном) TU.

OK, поэтому в этом случае foo является шаблоном, следовательно, определение видно во всех TU, поэтомувозможно, теоретически компилятор может исключить риск того, что он что-то сделает с адресом.Но в целом вы, конечно, не должны брать адреса или ссылки на несуществующие объекты; -)

23 голосов
/ 22 марта 2011

Если вы пишете статическую переменную const с инициализатором внутри объявления класса, это похоже на то, как если бы вы написали

class Bar
{
      enum { kConst = 1 };
}

, и GCC будет обрабатывать это так же, то есть у него нетадрес.

Правильный код должен быть

class Bar
{
      static const int kConst;
}
const int Bar::kConst = 1;
12 голосов
/ 07 января 2015

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

2 голосов
/ 19 ноября 2017

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

class Bar
{
  static constexpr int kConst() { return 1; };
};
2 голосов
/ 22 марта 2011

g ++ версия 4.3.4 принимает этот код (см. эту ссылку ).Но g ++ версии 4.4.0 отвергает его.

1 голос
/ 19 сентября 2017

Простой трюк: используйте + до того, как kConst передаст функцию.Это предотвратит получение константой ссылки, и таким образом код не будет генерировать запрос компоновщика к константному объекту, а вместо этого будет продолжать использовать значение константы времени компилятора.

1 голос
/ 22 марта 2011

Я думаю, что этот артефакт C ++ означает, что всякий раз, когда упоминается Bar::kConst, вместо него используется его буквальное значение.

Это означает, что на практике нет переменной, на которую можно ссылаться.

Возможно, вам придется сделать это:

void func()
{
  int k = kConst;
  foo(k);
}
0 голосов
/ 24 августа 2018

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...