Неопределенная ссылка на статический член класса

201

Может кто-нибудь объяснить, почему следующий код не скомпилируется? По крайней мере, на g ++ 4.2.4.

И еще интересно, почему он будет компилироваться, когда я приведу MEMBER к int?

#include <vector>

class Foo {  
public:  
    static const int MEMBER = 1;  
};

int main(){  
    vector<int> v;  
    v.push_back( Foo::MEMBER );       // undefined reference to `Foo::MEMBER'
    v.push_back( (int) Foo::MEMBER ); // OK  
    return 0;
}
Павел Пятковский
источник
Я отредактировал вопрос, чтобы сделать отступ для кода на четыре пробела вместо использования <pre> <code> </ code> </ pre>. Это означает, что угловые скобки не интерпретируются как HTML.
Стив Джессоп
stackoverflow.com/questions/16284629/… Вы можете обратиться к этому вопросу.
Тооба Икбал

Ответы:

199

Вам нужно где-то определить статический член (после определения класса). Попробуй это:

class Foo { /* ... */ };

const int Foo::MEMBER;

int main() { /* ... */ }

Это должно избавить от неопределенной ссылки.

Дрю Холл
источник
3
Полезно отметить, что встроенная статическая константная целочисленная инициализация создает целочисленную константу с заданной областью, адрес которой вы не можете получить, а вектор принимает ссылочный параметр.
Эван Теран
10
Этот ответ касается только первой части вопроса. Вторая часть гораздо интереснее: почему добавление NOP приводит к тому, что оно работает без внешнего объявления?
Нобар
32
Я просто потратил немало времени, выясняя, что если определение класса находится в заголовочном файле, то распределение статической переменной должно быть в файле реализации, а не в заголовке.
shanet
1
@shanet: Очень хороший момент - я должен был упомянуть это в своем ответе!
Дрю Хол
Но если я объявлю это как const, я не смогу изменить значение этой переменной?
Намрата
78

Проблема возникает из-за интересного столкновения новых функций C ++ и того, что вы пытаетесь сделать. Во-первых, давайте посмотрим на push_backподпись:

void push_back(const T&)

Ожидается ссылка на объект типа T. При старой системе инициализации такой член существует. Например, следующий код компилируется просто отлично:

#include <vector>

class Foo {
public:
    static const int MEMBER;
};

const int Foo::MEMBER = 1; 

int main(){
    std::vector<int> v;
    v.push_back( Foo::MEMBER );       // undefined reference to `Foo::MEMBER'
    v.push_back( (int) Foo::MEMBER ); // OK  
    return 0;
}

Это потому, что где-то есть реальный объект, в котором хранится это значение. Если, однако, вы переключаетесь на новый метод указания статических константных членов, как у вас выше, Foo::MEMBERбольше не является объектом. Это константа, несколько сродни

#define MEMBER 1

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

Дуглас Мейл
источник
2
спасибо, это помогло ... это может претендовать на stackoverflow.com/questions/1995113/strangest-language-feature, если его там еще нет ...
Андре Хольцнер
1
Также стоит отметить, что MSVC принимает некачественную версию без нареканий.
Porges
4
-1: Это просто неправда. Вы по-прежнему должны определять статические элементы, инициализируемые inline, когда они где- то используются в odr . Оптимизация компилятора может избавить вас от ошибки компоновщика, это не изменит. В этом случае ваше преобразование lvalue в rvalue (благодаря (int)приведению) происходит в блоке перевода с идеальной видимостью константы и Foo::MEMBERбольше не используется odr . Это отличается от первого вызова функции, где ссылка передается и оценивается в другом месте.
Гонки легкости на орбите
Как насчет void push_back( const T& value );? const&Можно связать со значениями.
Костас
59

Стандарт C ++ требует определения вашего статического константного члена, если определение как-то необходимо.

Требуется определение, например, если используется его адрес. push_backпринимает его параметр по константной ссылке, поэтому компилятору строго необходим адрес вашего члена, и вам нужно определить его в пространстве имен.

Когда вы явно приводите константу, вы создаете временный объект, и именно этот временный объект привязывается к ссылке (в соответствии со специальными правилами в стандарте).

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

Хотя странным образом это можно рассматривать как законное использование унарного оператора «+». В основном результатом unary +является значение rvalue, поэтому применяются правила привязки значений rvalue к ссылкам const, и мы не используем адрес нашего статического члена const:

v.push_back( +Foo::MEMBER );
Ричард Корден
источник
3
+1. Да, конечно, странно, что для объекта x типа T выражение «(T) x» может использоваться для привязки константной ссылки, в то время как обычное «x» не может. Мне нравится ваше наблюдение про "унарный +"! Кто бы мог подумать, что бедный маленький "унарный +" на самом деле имеет
смысл
3
Думая об общем случае ... Есть ли какой-либо другой тип объекта в C ++, обладающий тем свойством, что его (1) можно использовать как lvalue, только если он был определен, но (2) можно преобразовать в rvalue без определены?
j_random_hacker
Хороший вопрос, и, по крайней мере, на данный момент я не могу придумать никаких других примеров. Это, вероятно, только здесь, потому что комитет в основном просто использовал существующий синтаксис.
Ричард Корден
@RichardCorden: как унарный + решил это?
Blood-HaZaRd
1
@ Blood-HaZaRd: Перед ссылками Rvalue единственной перегрузкой push_backбыл const &. Непосредственное использование члена привело к тому, что член был привязан к ссылке, для которой требовался адрес. Тем не менее, добавление +создает временное значение члена. Ссылка затем связывается с этим временным, а не требует, чтобы член имел адрес.
Ричард Корден
10

Aaa.h

class Aaa {

protected:

    static Aaa *defaultAaa;

};

Aaa.cpp

// You must define an actual variable in your program for the static members of the classes

static Aaa *Aaa::defaultAaa;
iso9660
источник
1

Не знаю, почему работает приведение, но Foo :: MEMBER не выделяется до тех пор, пока Foo не загрузится в первый раз, и, поскольку вы никогда не загружаете его, он никогда не выделяется. Если бы у вас была ссылка на Foo где-то, это, вероятно, сработало бы.

Пол Томблин
источник
Я думаю, что вы отвечаете на свой вопрос: актерский состав работает, потому что он создает (временную) ссылку.
Яап Верстег
1

С C ++ 11, выше было бы возможно для основных типов, как

class Foo {
public:  
  static constexpr int MEMBER = 1;  
};

Эта constexprдеталь создает статическое выражение, а не статическую переменную, и это ведет себя как очень простое определение встроенного метода. Однако этот подход оказался немного шатким, когда в классах шаблонов использовались константные выражения C-строки.

starturtle
источник
Это оказалось важным для меня, потому что "static const int MEMBER = 1;" необходимо использовать MEMBER в коммутаторах, в то время как для использования его в векторах необходимо внешнее объявление, и вы не можете использовать оба в одно и то же время. Но выражение, которое вы здесь даете , работает для обоих, по крайней мере, с моим компилятором.
Бен Фармер
0

Что касается второго вопроса: push_ref принимает ссылку в качестве параметра, и вы не можете иметь ссылку на статический константный член класса / структуры. Как только вы вызываете static_cast, создается временная переменная. И ссылку на этот объект можно передать, все работает просто отлично.

Или, по крайней мере, мой коллега, который решил это, сказал так.

Quarra
источник