Странные неопределенные символы статических констант внутри структуры / класса - PullRequest
32 голосов
/ 03 февраля 2011

Либо я очень устал, либо происходит что-то странное, о чем я не знаю, потому что код ниже приводит к неопределенным символам для 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.Также хорошо при определении статических целых вне класса / структуры (глобальная в этом простом случае).Однако, как только они окажутся внутри, компоновщик не сможет их найти.

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

Ответы [ 5 ]

42 голосов
/ 03 февраля 2011

Требуется определение

Код, который вы указали, нестандартен. Хотя вы можете предоставить инициализаторы для константных статических членов 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);
}

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

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

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

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

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

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

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

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

Стандарты

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

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

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

C ++ 03 формулировка:

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

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

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

3.2 говорит следующее о odr-use:

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

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

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

3 голосов
/ 03 февраля 2011

Если вы просто хотите получить целые значения, вы также можете определить 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

2 голосов
/ 12 июля 2012

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

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

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

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

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

2 голосов
/ 03 февраля 2011

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

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

const int Foo::A = 1;
const int Foo::B = 2;
1 голос
/ 04 февраля 2011

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

#include <algorithm>

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

int main()
{
    return std::min(Foo::A, Foo::B);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...