Ініціалізувати статичні змінні в класі C ++?


81

Я помітив, що деякі мої функції в класі насправді не мають доступу до об'єкта, тому я зробив їх static. Тоді компілятор сказав мені, що всі змінні, до яких вони мають доступ, також повинні бути статичними - ну, цілком зрозумілими поки що. У мене є купа рядкових змінних, таких як

string RE_ANY = "([^\\n]*)";
string RE_ANY_RELUCTANT = "([^\\n]*?)";

і так далі в класі. Тоді я створив їх усіх, static constбо вони ніколи не змінюються. Однак моя програма компілюється лише в тому випадку, якщо я переміщую їх з класу: В іншому випадку MSVC ++ 2010 скаржиться на те, "що в класі можуть бути ініціалізовані лише статичні постійні інтегральні змінні".

Ну це прикро. Чи є обхідне рішення? Я хотів би залишити їх у класі, до якого вони належать.

Відповіді:


131

Їх неможливо ініціалізувати всередині класу, але їх можна ініціалізувати поза класом, у вихідному файлі:

// inside the class
class Thing {
    static string RE_ANY;
    static string RE_ANY_RELUCTANT;
};

// in the source file
string Thing::RE_ANY = "([^\\n]*)";
string Thing::RE_ANY_RELUCTANT = "([^\\n]*?)";

Оновлення

Я щойно помітив перший рядок вашого запитання - ви не хочете робити ці функції static, ви хочете їх зробити const. Виконання їх staticозначає, що вони більше не пов’язані з об’єктом (тому вони не можуть отримати доступ до будь-яких нестатичних членів), а статичні дані означають, що вони будуть спільно використовуватися з усіма об’єктами цього типу. Це цілком може бути не тим, що ви хочете. Зробити їх constпросто означає, що вони не можуть змінити жодного учасника, але все одно можуть отримати до них доступ.


2
Вони не мають доступу до нічого в об'єкті, лише до тимчасових змінних, які надаються їм як посилальні аргументи. Тому вони, мабуть, повинні бути і, constі static.
Фелікс Домбек,

8
@Felix: це не неможливо, constозначає , що вона не змінює this... і немає thisдля staticметодів.
Matthieu M.

2
@ androidplusios.design: Я не слідкую. Це потрібно, stringоскільки це визначення змінної, для якого потрібен специфікатор типу, а тип - це string. Ви можете розмістити ()декларатор, якщо хочете, це не змінює значення.
Майк Сеймур

4
@ androidplusios.design: Це справді бачить. Але ви повинні використовувати синтаксис визначення для його визначення та ініціалізації, і це включає специфікатор типу, незалежно від того, оголошено він раніше чи ні. Так вказана мова.
Майк Сеймур

4
@ androidplusios.design: Або, якщо ви запитуєте, чому синтаксис є таким, яким він є, з усіма його невідповідностями, надмірностями та неоднозначностями: оскільки він розвивався протягом багатьох десятиліть із набагато простіших мов.
Майк Сеймур

33

Майк Сеймур дав вам правильну відповідь, але, щоб додати ...
C ++ дозволяє декларувати і визначати у своєму тілі класу лише статичні цілі типи const , як розповідає компілятор. Отже, ви можете насправді зробити:

class Foo
{
    static const int someInt = 1;
    static const short someShort = 2;
    // etc.
};

І ви не можете цього зробити з будь-яким іншим типом, у цьому випадку ви повинні визначити їх у своєму файлі .cpp.


17

З C ++ 11 це можна зробити всередині класу за допомогою constexpr.

class stat {
    public:
        // init inside class
        static constexpr double inlineStaticVar = 22;
};

Тепер до змінної можна отримати доступ за допомогою:

stat::inlineStaticVar

1
Безумовно, це приємно для ints, double, покажчиків тощо. Однак це не працює зі рядком із питання, оскільки рядок не є літералом чи посиланням.
Рой Дантон,

1
Влучне зауваження. Хоча, ти міг би зробити constexpr char RE_ANY[] = "([^\\n]*)";або constexpr std::string_view RE_ANY("([^\\n]*)", 9);.
0ax1

16

Статичні змінні члени повинні бути оголошені в класі, а потім визначені поза ним!

Тут немає обхідних шляхів, просто помістіть їхнє фактичне визначення у вихідний файл.


З вашого опису пахне, ніби ви не використовуєте статичні змінні правильно. Якщо вони ніколи не змінюються, замість цього слід використовувати постійну змінну, але ваш опис занадто загальний, щоб сказати щось більше.

Статичні змінні-члени завжди містять одне і те ж значення для будь-якого екземпляра вашого класу: якщо ви зміните статичну змінну одного об'єкта, вона зміниться і для всіх інших об'єктів (і насправді ви також можете отримати до них доступ без екземпляра класу - тобто: об'єкт).


3
Зараз вони const - вони просто повинні бути статичними, щоб я міг використовувати їх у статичних функціях-членах. У чому причина цього правила, що вони повинні бути оголошені всередині та визначені поза класом? Для мене це не має особливого сенсу.
Фелікс Домбек,

2
@Felix Dombek: Я думаю, що причина в тому, що клас (/ може бути) оголошений для кожного вихідного файлу, який ви компілюєте та посилаєте, але фактичні змінні повинні бути визначені лише один раз. Це та сама причина, чому вам потрібно явно оголосити externзмінні, визначені в інших вихідних файлах.
peoro

1
@peoro: Це здається розумним! Але чому тоді це дозволено для цілісних типів даних? Тоді і цього не можна дозволяти ...
Фелікс Домбек

1
@Felix Dombek: це також заборонено для цілісних типів. Якщо ви визначите цю структуру: struct A { static int x; };і спробуєте отримати доступ A::xбез визначення int A::x;у вихідному файлі, ваш код не буде посилатися.
peoro

2
@Felix Dombek: це не стандартна скарга. ISO C ++ забороняє ініціалізацію в класі нестандартних статичних членів. Ви можете зробити це лише для інтегральних статичних членів const, і це тому, що статичні інтегральні змінні const насправді не будуть поміщені в пам'ять, а використовуватимуться як константи під час компіляції.
peoro

9

Я вважаю, що варто додати, що статична змінна - це не те саме, що і постійна змінна.

використання константи змінної в класі

struct Foo{
    const int a;
    Foo(int b) : a(b){}
}

і ми оголосимо це так

fooA = new Foo(5);
fooB = new Foo(10);
// fooA.a = 5;
// fooB.a = 10;

Для статичної змінної

struct Bar{
    static int a;
    Foo(int b){
        a = b;
    }
}
Bar::a = 0; // set value for a

який використовується так

barA = new Bar(5);
barB = new Bar(10);
// barA.a = 10;
// barB.a = 10;
// Bar::a = 10;

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

Як і у випадку з Bar, їх значення є лише одним для Bar: незалежно від кількості екземплярів Bar. Усі вони поділяють це значення, ви також можете отримати до нього доступ із будь-яких екземплярів Bar. Статична змінна також дотримується правил для загального / приватного, тому ви можете зробити так, щоб лише екземпляри Bar могли читати значення Bar :: a;


1
ха ... смішно, ніхто не вказував на моє жахливе непотрібне використання "нового". Це питання насправді не турбує, але так, уникайте використання «нового», коли вам не потрібно, і ви більш-менш ніколи цього не робите.
thecoshman

Видаліть своє жахливе, непотрібне використання new. ;-)
Рой Дантон

1
Я вважаю, що це тепер є історичним прикладом того, як старий код був поганим. На даний момент, я думаю, було б поганою послугою видалити його.
thecoshman

8

Просто додати поверх інших відповідей. Для того, щоб ініціалізувати складний статичний член , ви можете зробити це наступним чином:

Заявіть про свій статичний член, як зазвичай.

// myClass.h
class myClass
{
static complexClass s_complex;
//...
};

Створіть невелику функцію для ініціалізації вашого класу, якщо це не тривіально. Це буде викликано лише тоді, коли статичний член ініціалізується. (Зверніть увагу, що буде використаний конструктор копіювання complexClass, тому він повинен бути чітко визначений).

//class.cpp    
#include myClass.h
complexClass initFunction()
{
    complexClass c;
    c.add(...);
    c.compute(...);
    c.sort(...);
    // Etc.
    return c;
}

complexClass myClass::s_complex = initFunction();

5

Деякі відповіді здаються дещо оманливими .

Вам не потрібно ...

  • Призначайте значення деякому статичному об’єкту при ініціалізації, оскільки присвоєння значення необов’язкове .
  • Створіть інший .cppфайл для ініціалізації, оскільки це можна зробити в тому ж файлі заголовка .

Крім того, ви навіть можете ініціалізувати статичний об'єкт у тому ж обсязі класу, як звичайна змінна, використовуючи inlineключове слово.


Ініціалізувати без значень в одному файлі

#include <string>
class A
{
    static std::string str;
    static int x;
};
std::string A::str;
int A::x;

Ініціалізуйте значеннями в одному файлі

#include <string>
class A
{
    static std::string str;
    static int x;
};
std::string A::str = "SO!";
int A::x = 900;

Ініціалізуйте в тому ж обсязі класу, використовуючи inlineключове слово

#include <string>
class A
{
    static inline std::string str = "SO!";
    static inline int x = 900;
};

2
Найкраща відповідь з усіх них. Я додав, inlineі всі помилки зв’язування зникають.
Контанго,

'вбудовані змінні - це розширення c ++ 17'. Це повідомлення я отримую
Ssenyonjo

@Ssenyonjo Ви повинні встановити мовний стандарт C ++ на C ++ 17 або вище. тобто у Visual Studio перейдіть до проекту -> Властивості -> C / C ++ -> Мова -> Стандарт мови C ++ -> ISO C ++ 17 або ISO C ++ Останні. А в країнах GCC використовуйте прапор -std=c++17або -std=c++20ін.
Бейондо,

2

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

Дивіться тут приклад:

Статична ініціалізація члена в шаблоні класу


1

За бажанням, перемістіть усі свої константи у файл .cpp без оголошення у файлі .h. Використовуйте анонімний простір імен, щоб зробити їх невидимими поза модулем cpp.

// MyClass.cpp

#include "MyClass.h"

// anonymous namespace
namespace
{
    string RE_ANY = "([^\\n]*)";
    string RE_ANY_RELUCTANT = "([^\\n]*?)";
}

// member function (static or not)
bool MyClass::foo()
{
    // logic that uses constants
    return RE_ANY_RELUCTANT.size() > 0;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.