Неопределенная ссылка на статический член класса
Может кто-нибудь объяснить, почему следующий код не скомпилируется? По крайней мере, на 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;
}
9 ответов
Вам нужно определить где-то статический член (после определения класса). Попробуй это:
class Foo { /* ... */ };
const int Foo::MEMBER;
int main() { /* ... */ }
Это должно избавиться от неопределенной ссылки.
Проблема возникает из-за интересного столкновения новых функций 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
Но без головной боли макроса препроцессора (и с безопасностью типа). Это означает, что вектор, ожидающий ссылку, не может его получить.
Стандарт C++ требует определения вашего статического константного члена, если определение как-то необходимо.
Требуется определение, например, если используется его адрес. push_back
принимает его параметр по константной ссылке, поэтому компилятору строго необходим адрес вашего члена, и вам нужно определить его в пространстве имен.
Когда вы явно приводите константу, вы создаете временный объект, и именно этот временный объект привязывается к ссылке (в соответствии со специальными правилами в стандарте).
Это действительно интересный случай, и я на самом деле думаю, что стоит поднять проблему, чтобы изменить стандартное поведение, чтобы иметь то же поведение для вашего постоянного члена!
Хотя странным образом это можно рассматривать как законное использование унарного оператора "+". В основном результат unary +
является значением rvalue, поэтому применяются правила привязки значений rvalue к ссылкам const, и мы не используем адрес нашего статического члена const:
v.push_back( +Foo::MEMBER );
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;
В C ++ 17 есть более простое решение, использующее
inline
переменные:
struct Foo{
inline static int member;
};
Это определение
member
, а не только его декларацию. Подобно встроенным функциям, несколько идентичных определений в разных единицах перевода не нарушают ODR. Больше нет необходимости выбирать любимый файл .cpp для определения.
Просто дополнительная информация:
C++ позволяет «определять» константные статические типы целочисленных и перечислимых типов в качестве членов класса. Но на самом деле это не определение, а просто «маркер инициализации».
Вы все равно должны написать определение своего члена вне класса.
9.4.2/4 - Если статический элемент данных имеет тип константного интегрального или константного перечисления, его объявление в определении класса может указывать константный инициализатор, который должен быть интегральным константным выражением (5.19). В этом случае член может появляться в целочисленных постоянных выражениях. Этот член по-прежнему должен быть определен в области пространства имен, если он используется в программе, а определение области пространства имен не должно содержать инициализатор.
С C++11, выше было бы возможно для основных типов, как
class Foo {
public:
static constexpr int MEMBER = 1;
};
constexpr
part создает статическое выражение, а не статическую переменную, и это ведет себя как очень простое определение встроенного метода. Однако этот подход оказался немного шатким, когда в классах шаблонов использовались константные выражения C-строки.
Не знаю, почему работает приведение, но Foo::MEMBER не выделяется до тех пор, пока Foo не загрузится в первый раз, и, поскольку вы никогда не загружаете его, он никогда не выделяется. Если бы у вас была ссылка на Foo где-то, это, вероятно, сработало бы.
Что касается второго вопроса: push_ref принимает ссылку в качестве параметра, и вы не можете иметь ссылку на статический константный член класса / структуры. Как только вы вызываете static_cast, создается временная переменная. И ссылку на этот объект можно передать, все работает просто отлично.
Или, по крайней мере, мой коллега, который решил это, сказал так.