Невизначена посилання на статичний член класу


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/… Ви можете звернутися до цього питання.
Tooba Iqbal

Відповіді:


199

Вам потрібно десь визначити статичний член (після визначення класу). Спробуйте це:

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

const int Foo::MEMBER;

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

Це повинно позбутися невизначеної посилання.


3
Хороша точка, вбудована статична цілочисельна цілісна ініціалізація створює масштабну цілу константу, якої ви не можете прийняти за адресою, а вектор приймає контрольний параметр.
Еван Теран

10
Ця відповідь стосується лише першої частини питання. Друга частина набагато цікавіша: чому додавання NOP-ролика спрацьовує, не вимагаючи зовнішнього оголошення?
nobar

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;
}

Це тому, що десь є фактичний об’єкт, який має це значення, збережене в ньому. Якщо, однак, ви переходите на новий метод визначення статичних членів const, як у вас вище, Foo::MEMBERбільше не є об'єктом. Це константа, дещо схожа на:

#define MEMBER 1

Але без головних болів препроцесорного макроса (і з безпекою типу). Це означає, що вектор, який очікує посилання, не може отримати його.


2
дякую, що допомогло ... що могло претендувати на stackoverflow.com/questions/1995113/strangest-language-feature, якщо його вже немає ...
Андре Хольцнер

1
Також варто зауважити, що MSVC приймає версію, яка не використовується, без скарг.
porges

4
-1: Це просто неправда. Ви все ще повинні визначити статичні члени, ініціалізовані в рядку, коли вони десь використовуються . Оптимізація компілятора може позбутися вашої помилки лінкера. У цьому випадку ваша конверсія lvalue-to-rvalue (завдяки викладенню (int)) відбувається в блоці перекладу з ідеальною видимістю константи, і значення Foo::MEMBERбільше не використовується . Це на відміну від першого виклику функції, де посилання передається і оцінюється в іншому місці.
Гонки легкості по орбіті

Про що void push_back( const T& value );? const&'s може пов'язуватися з rvalues.
Костас

59

Стандарт C ++ вимагає визначення вашого статичного члена const, якщо визначення якимось чином необхідне.

Визначення потрібно, наприклад, якщо використовується його адреса. push_backприймає його параметр за посиланням const, і так суворо компілятору потрібна адреса вашого члена, і вам потрібно визначити його в просторі імен.

Коли ви явно передаєте константу, ви створюєте тимчасову, і саме ця тимчасова пов'язана з посиланням (згідно спеціальних правил у стандарті).

Це справді цікавий випадок, і я насправді думаю, що варто поставити питання, щоб змінити std, щоб мати таку саму поведінку для вашого постійного члена!

Хоча, дивним чином, це можна розглядати як легітимне використання одинарного оператора "+". В основному результатом значення unary +є оцінку, тому застосовуються правила прив'язки rvalues ​​до const посилань, і ми не використовуємо адресу нашого статичного члена const:

v.push_back( +Foo::MEMBER );

3
+1. Так, звичайно дивно, що для об'єкта x типу T вираз "(T) x" може бути використаний для прив'язки const ref, тоді як звичайний "x" не може. Я люблю ваші спостереження щодо "одинарних +"! Хто б міг подумати, що бідний маленький "унар +" насправді мав користь ... :)
j_random_hacker

3
Розмірковуючи про загальний випадок ... Чи є якийсь інший тип об'єкта в C ++, який має властивість, що він (1) може використовуватися як значення, лише якщо він був визначений, але (2) може бути перетворений в ревальвіє, не будучи визначений?
j_random_hacker

Добре запитання, і принаймні на даний момент я не можу придумати жодних інших прикладів. Це, мабуть, лише тому, що комітет здебільшого просто використовував існуючий синтаксис.
Річард Корден

@RichardCorden: як унар + вирішив це?
Blood-HaZaRd

1
@ Blood-HaZaRd: Перед оцінкою посилань єдиним перевантаженням push_backбуло a 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;

1

Не маю ідеї, чому працює акторський склад, але Foo :: MEMBER не виділяється до першого завантаження Foo, і оскільки ви ніколи не завантажуєте його, він ніколи не виділяється. Якби ви десь мали посилання на Foo, це, мабуть, спрацювало б.


Я думаю, ти відповідаєш на власне запитання: Акторський склад працює тому, що він створює (тимчасову) посилання.
Jaap Versteegh

1

З C ++ 11, вищезгадане було б можливим для основних типів як

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

constexprЧастина створює статичне вираження на відміну від статичної змінної - і що поводиться так само , як дуже просте визначення методу рядний. Підхід виявився дещо хитким із конспектами C-string у класах шаблонів.


Це виявилося для мене важливим, оскільки "статичний const int МЕМБР = 1;" потрібно використовувати MEMBER в комутаторах, тоді як зовнішнє оголошення необхідно використовувати його у векторах, і ви не можете мати обох одночасно. Але вираз, який ви даєте тут , працює як для мого упорядника.
Бен Фармер

0

Щодо другого питання: push_ref приймає посилання як параметр, і ви не можете мати посилання на статичний const memeber класу / структури. Після виклику static_cast створюється тимчасова змінна. І посилання на цей об’єкт можна передати, все працює просто чудово.

Або принаймні мій колега, який вирішив це, сказав так.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.