Визначення статичних const цілих членів у визначенні класу


109

Я розумію, що C ++ дозволяє визначати статичні члени const всередині класу, якщо це цілий тип.

Чому, таким чином, наступний код дає мені помилку лінкера?

#include <algorithm>
#include <iostream>

class test
{
public:
    static const int N = 10;
};

int main()
{
    std::cout << test::N << "\n";
    std::min(9, test::N);
}

Я отримую помилку:

test.cpp:(.text+0x130): undefined reference to `test::N'
collect2: ld returned 1 exit status

Цікаво, що якщо я коментую виклик до std :: min, код компілюється і посилається просто чудово (навіть якщо тест :: N також посилається на попередній рядок).

Будь-яка ідея, що відбувається?

Мій компілятор - gcc 4.4 в Linux.


3
Чудово працює на Visual Studio 2010.
Щеня

4
Ця точна помилка пояснюється на gcc.gnu.org/wiki/…
Джонатан

У конкретному випадку charви можете визначити його замість як constexpr static const char &N = "n"[0];. Зверніть увагу на &. Я думаю, це працює, тому що буквальні рядки визначаються автоматично. Я хоч і хвилююся з цього приводу - це може поводитися дивно у файлі заголовка серед різних одиниць перекладу, оскільки рядок, ймовірно, буде за кількома різними адресами.
Аарон Мак-Дейд

1
Це питання є маніфестом того, наскільки бідним є відповідь C ++ на "не використовувати #defines для констант".
Йоганнес Оверманн

1
@JohannesOvermann У зв'язку з цим я хочу зазначити використання вбудованих для глобальних змінних з моменту C ++ 17 inline const int N = 10, який, наскільки мені відомо, все ще має місце десь визначене лінкером. Введене ключове слово також може бути використане в цьому випадку для надання визначення статичної змінної всередині тесту на визначення класу.
Вормер

Відповіді:


72

Я розумію, що C ++ дозволяє визначати статичні члени const всередині класу, якщо це цілий тип.

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

http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=/com.ibm.xlcpp8a.doc/language/ref/cplr038.htm

Цікаво, що якщо я коментую виклик до std :: min, код компілюється і посилається просто чудово (навіть якщо тест :: N також посилається на попередній рядок).

Будь-яка ідея, що відбувається?

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

Ось розділ / вірш:

9.4.2 / 4 - Якщо staticчлен даних має constінтегральний чи constперелічуючий тип, його декларація у визначенні класу може визначати константа-ініціалізатор, яка має бути інтегральним постійним виразом (5.19). У цьому випадку член може з'являтися в цілісних постійних виразах. Член все ж повинен бути визначений в області простору імен, якщо він використовується в програмі, а визначення сфери простору імен не повинно містити ініціалізатор .

Дивіться відповідь Чу для можливого вирішення.


Я бачу, це цікаво. У цьому випадку, в чому різниця між наданням значення в точці декларування та порівнянням із наданням значення в точці визначення? Який із них рекомендується?
HighCommander4

Ну, я вважаю, що ви можете піти без визначення до тих пір, поки фактично ніколи не будете "використовувати" змінну. Якщо ви використовуєте його лише як частину постійного виразу, то змінна ніколи не використовується. В іншому випадку, здається, немає великої різниці, крім того, що зможете побачити значення в заголовку - що може бути, а може і не бути тим, що вам потрібно.
Едвард Странд

2
Коротка відповідь - статичний const x = 1; є rvalue, але не lvalue. Значення доступне як константа під час компіляції (ви можете розміряти масив за допомогою нього) статичного const y; [немає ініціалізатора] повинен бути визначений у файлі cpp і може використовуватися як rvalue або lvalue.
Дейл Вілсон

2
Було б добре, якби вони могли це розширити / покращити. Ініціалізовані, але не визначені об'єкти, на мою думку, повинні трактуватися так само, як і літерали. Наприклад, нам дозволяється прив’язати буквальне значення 5до a const int&. То чому б не трактувати ОП test::Nяк відповідний буквальний текст?
Аарон Мак-Дейд

Цікаве пояснення, дякую! Це означає, що в C ++ статичний const int все ще не замінює ціле число #defines. enum завжди підписаний лише int, тому треба використовувати enum-класи для окремих констант. Для мене було б цілком очевидним виродження постійної декларації з постійними і знаними значеннями в буквальну константу, в який спосіб це складеться без проблем. У C ++ ще довгий шлях ...
Йоханнес Оверманн

51

Приклад Bjarne Stroustrup у своєму FAQ + щодо C ++ підказує, що ви правильні, і вам потрібне визначення лише тоді, коли ви приймете адресу.

class AE {
    // ...
public:
    static const int c6 = 7;
    static const int c7 = 31;
};

const int AE::c7;   // definition

int f()
{
    const int* p1 = &AE::c6;    // error: c6 not an lvalue
    const int* p2 = &AE::c7;    // ok
    // ...
}

Він каже: "Ви можете взяти адресу статичного члена, якщо (і лише якщо) він має позакласне визначення" . Що говорить про те, що це діятиме інакше. Можливо, ваша функція min викликає адреси якось поза кадром.


2
std::minприймає його параметри за посиланням, тому необхідне визначення.
Rakete1111

Як би я записав визначення, якщо AE - клас шаблонів AE <клас T> і c7 не є int, а T :: size_type? У мене заголовок значення ініційований на "-1" у заголовку, але кланг говорить невизначене значення, і я не знаю, як написати визначення.
Фабіан

@Fabian Я подорожую по телефону і трохи зайнятий ... але я б подумав, що ваш коментар звучить так, що його найкраще написати як нове запитання. Створіть MCVE, включаючи помилку, яку ви отримаєте, а також, можливо, киньте те, що говорить gcc. Б'юсь об заклад, що люди швидко скажуть вам, що робити.
HostileFork каже, що не довіряйте SE

@HostileFork: Коли ви пишете MCVE, ви іноді самі розбираєте рішення. У моєму випадку відповідь полягає в тому, template<class K, class V, class C> const typename AE<K,V,C>::KeyContainer::size_type AE<K,V,C>::c7;де KeyContainer є typedef std :: vector <K>. Потрібно перелічити всі параметри шаблону та написати ім'я типу, оскільки це залежний тип. Можливо, хтось вважатиме цей коментар корисним. Однак зараз мені цікаво, як експортувати це в DLL, оскільки клас шаблонів, звичайно, знаходиться в заголовку. Чи потрібно експортувати c7 ???
Фабіан

24

Ще один спосіб зробити це для цілих типів у будь-якому випадку - це визначити константи як перерахунки в класі:

class test
{
public:
    enum { N = 10 };
};

2
І це, мабуть, вирішило б проблему. Коли N використовується як параметр min (), це спричинить тимчасове створення, а не спробу посилатися на нібито існуючу змінну.
Едвард Странд

Це мало перевагу в тому, що його можна зробити приватним.
Агостіно

11

Не тільки інт. Але ви не можете визначити значення в декларації класу. Якщо у вас є:

class classname
{
    public:
       static int const N;
}

у файлі .h тоді ви повинні мати:

int const classname::N = 10;

у файлі .cpp.


2
Мені відомо, що ви можете оголосити змінну будь-якого типу всередині декларації класу. Я сказав, що думаю, що статичні цілісні константи також можуть бути визначені всередині декларації класу. Це не так? Якщо ні, то чому компілятор не видає помилки в рядку, де я намагаюся визначити це всередині класу? Більше того, чому строка std :: cout не спричиняє помилку лінкера, а рядок std :: min?
HighCommander4

Ні, не можна визначити статичних членів у декларації класу, оскільки ініціалізація видає код. На відміну від вбудованої функції, яка також випромінює код, статичне визначення є унікальним у всьому світі.
Amardeep AC9MF

@ HighCommander4: Ви можете надати ініціалізатор для static constінтегрального члена у визначенні класу. Але це все ще не визначає цього члена. Дивіться відповідь Ноя Робертса для деталей.
ANT

9

Ось ще один спосіб вирішити проблему:

std::min(9, int(test::N));

(Я думаю, що відповідь Божевільного Едді правильно описує, чому існує проблема.)


5
чи навітьstd::min(9, +test::N);
Томілов Анатолій

Ось тут головне питання: чи все це оптимально? Я не знаю про вас, хлопці, але моя велика прихильність до пропуску визначення полягає в тому, що він повинен займати не пам'ять, ані накладні витрати при використанні const статики.
Opux

6

Станом на C ++ 11 ви можете використовувати:

static constexpr int N = 10;

Це теоретично все-таки вимагає від вас визначити константу у .cpp-файлі, але поки ви не приймете його адресу, Nмалоймовірно, що будь-яка реалізація компілятора призведе до помилки;).


А що, якщо вам потрібно передати значення як аргумент типу 'const int &', як у прикладі? :-)
Вормер

Це чудово працює. Ти таким чином не інформуєш N, а просто передаєш посилання const на тимчасове. wandbox.org/permlink/JWeyXwrVRvsn9cBj
Carlo Wood

C ++ 17, можливо, не C ++ 14, і навіть не C ++ 17 у попередніх версіях gcc 6.3.0 і новіших, це не стандартна річ. Але дякую, що згадуєте про це.
Вормер

Ага так, ти маєш рацію. Я не пробував c ++ 14 на wandbox. Ну добре, це та частина, де я сказав "Це теоретично все ще вимагає від вас визначення константи". Отже, ви праві, що це не "стандарт".
Карло Вуд

3

C ++ дозволяє визначати статичні члени const всередині класу

Ні, 3.1 §2 сказано:

Декларація - це визначення, якщо воно не декларує функцію, не вказуючи тіло функції (8.4), воно містить специфікатор зовнішнього (7.1.1) або специфікацію зв'язку (7.5) і не ініціалізатор, ані тіло функції, воно не оголошує статичні дані член у визначенні класу (9.4), це декларація імені класу (9.1), це непрозора декларація enum (7.2), або це декларація typedef (7.1.3), декларація використання (7.3). 3) або користувачу-розпорядження (7.3.4).

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