Подтвердить что ты не робот

Weird undefined символы статических констант внутри struct/class

Либо я очень устал, либо что-то странное происходит, что я не знаю, потому что код ниже , что приводит к символам undefined для Foo:: A и Foo:: B при связывании. Это минимизировано настолько, насколько я мог, из более крупного проекта, но показывает суть того, что я ищу.

#include <algorithm>

struct Foo
{
    static const int A = 1;
    static const int B = 2;
};

int main()
{
    return std::min(Foo::A, Foo::B);
}

Без шаблона функции std:: min он отлично работает, т.е. просто возвращает Foo:: A. Также прекрасно, когда вы определяете статические ints вне класса/структуры (глобальные в этом простом случае). Однако, как только они будут внутри, линкер не может их найти.

Может кто-нибудь объяснить, что происходит?

4b9b3361

Ответ 1

Необходимое определение

Код, который вы предоставили, является нестандартным. Хотя вы можете предоставить инициализаторы для константных статических членов int непосредственно в классе, вам все равно необходимо предоставить отдельные определения. Это странно, что-то неожиданное, но вы должны написать его вот так:

#include <algorithm>

struct Foo
{
    static const int A = 1;
    static const int B = 2;
};

const int Foo::A;
const int Foo::B;

int main()
{
    return std::min(Foo::A, Foo::B);
}

Цитата из стандарта можно найти в аналогичном вопросе в константных и статических спецификациях в С++

Почему иногда код "работает" без определения?

Что касается того, почему вы часто можете обойтись даже без предоставления определения: если вы используете эти члены только в постоянных выражениях, компилятор будет всегда решать их напрямую и не будет доступа для разрешения компоновщика. Только когда вы используете его каким-то образом, который не может быть обработан компилятором напрямую, и только в этом случае компоновщик обнаружит символ undefined. Я думаю, это, вероятно, ошибка в компиляторе Visual Studio, но с учетом характера ошибки я сомневаюсь, что она будет когда-либо исправлена.

Почему ваш источник попадает в категорию "компоновщик" - это то, чего я не вижу, нужно было бы разбирать std:: min, чтобы понять это. Примечание. Когда я пробовал его онлайн с GCC, он работал, ошибка не обнаруживалась.

Альтернатива: используйте enum

Другой альтернативой является использование перечисления. Эта версия также может пригодиться, когда вы нажмете на старый компилятор, который не поддерживает инициализаторы static in int int inline (например, Visual Studio 6). Обратите внимание, однако, что при std:: min вы сталкиваетесь с другими проблемами с перечислениями, и вам нужно использовать явное инстанцирование или кастинг или иметь как A, так и B в одном названии enum, как в ответ от Nawaz:

struct Foo
{
    enum {A = 1};
    enum {B = 2};
};

int main()
{
    return std::min<int>(Foo::A, Foo::B);
}

Стандарты

Примечание: даже Часто задаваемые вопросы по Stroustrup С++ ошибочно и не требует определения строго так, как это делает стандарт:

Вы можете взять адрес статического члена, если (и только если) он имеет определение вне класса

Определение , требуемое стандартом в 9.4.2:

С++ 03:

Член все еще должен быть определен в области пространства имен, если он используется в программе, и определение области пространства имен не должно содержать инициализатор

С++ 11 формулировка 9.4.2 немного отличается:

3 Элемент все еще должен быть определен в области пространства имен, если он используется в odr (3.2) в программе

3.2 говорится следующее о использовании odr:

3 Переменная x, имя которой отображается как потенциально вычисленное выражение ex, используется odr, если только x не является объектом, удовлетворяющим требованиям к появлению в постоянном выражении (5.19), а ex является элементом набора потенциальных результатов выражения e, где либо преобразование lvalue-to-rvalue (4.1) применяется к e, либо e является выражением отбрасываемого значения (раздел 5).

4 Каждая программа должна содержать ровно одно определение каждой не-встроенной функции или переменной, которая используется в этой программе odr; не требуется диагностика.

Я должен признать, что не знаю, каковы точные последствия формулировки С++ 11, поскольку я не понимаю правила использования odr.

Ответ 2

Если вам нужны только интегральные значения, вы также можете определить enum:

#include <algorithm>

struct Foo
{
    enum integrals { A = 1, B = 2} ;
};

int main()
{
    return std::min(Foo::A, Foo::B);
}

Этого более чем достаточно. Никакого объявления не требуется вне класса!

Онлайн-демонстрация: http://www.ideone.com/oE9b5

Ответ 3

Вы должны определить статические константы вне определения класса.

struct Foo {
    static const int A;
    static const int B;
};

const int Foo::A = 1;
const int Foo::B = 2;

Ответ 4

Видя, как вы в основном используете struct как пространство имен, почему бы просто не использовать пространство имен:

#include <algorithm>

namespace Foo
{
    const int A = 1;
    const int B = 2;
};

int main()
{
    return std::min(Foo::A, Foo::B);
}

Ответ 5

Здесь есть хорошие ответы, но еще одна вещь, которую следует отметить, состоит в том, что параметры std::min() являются ссылками, для которых требуются адреса переданных в переменных, и поскольку эти переменные не попадают в объект файл для блока компиляции, компоновщик не может разрешить свои адреса.

Вероятно, вы получаете это в не оптимизированной сборке, правильно?

Я уверен, что вы не получите это с помощью gcc, если вы включите оптимизацию. Вызов std::min() будет вставлен и ссылки исчезнут.

Кроме того, если вы должны назначить Foo::A и Foo::B двум локальным переменным непосредственно перед вызовом std::min(), эта проблема также исчезнет.

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