Как инициализировать частные статические члены в C ++?

521

Каков наилучший способ инициализации частного статического члена данных в C ++? Я попробовал это в моем заголовочном файле, но он дает мне странные ошибки компоновщика:

class foo
{
    private:
        static int i;
};

int foo::i = 0;

Я предполагаю, что это потому, что я не могу инициализировать частного члена вне класса. Так каков лучший способ сделать это?

Джейсон Бейкер
источник
2
Привет Джейсон. Я не нашел комментарий по умолчанию для инициализации статических элементов (особенно интегральных). Фактически вам нужно написать int foo :: i, чтобы компоновщик мог его найти, но он будет автоматически инициализирован 0! Этой строки будет достаточно: int foo :: i; (Это действительно для всех объектов, хранящихся в статической памяти, компоновщик отвечает за инициализацию статических объектов.)
Нико
1
Ответы ниже не относятся к шаблону класса. Они говорят: инициализация должна идти в исходный файл. Для шаблонного класса это ни невозможно, ни необходимо.
Иоахим W
7
С ++ 17 позволяет инлайн инициализации статических элементов данных (даже для нецелых типов): inline static int x[] = {1, 2, 3};. См. En.cppreference.com/w/cpp/language/static#Static_data_members
Владимир Решетников

Ответы:

557

Объявление класса должно быть в заголовочном файле (или в исходном файле, если он не используется совместно).
Файл: foo.h

class foo
{
    private:
        static int i;
};

Но инициализация должна быть в исходном файле.
Файл: foo.cpp

int foo::i = 0;

Если инициализация находится в файле заголовка, то каждый файл, который включает файл заголовка, будет иметь определение статического члена. Таким образом, на этапе компоновки вы получите ошибки компоновщика, так как код для инициализации переменной будет определен в нескольких исходных файлах. Инициализация static int iдолжна выполняться вне какой-либо функции.

Примечание: Мэтт Кертис: указывает, что C ++ позволяет упростить выше , если статическая переменная - член имеет сопзЬ типа Int (например int, bool, char). Затем вы можете объявить и инициализировать переменную-член непосредственно внутри объявления класса в заголовочном файле:

class foo
{
    private:
        static int const i = 42;
};
Мартин Йорк
источник
4
Да. Но я предполагаю, что вопрос был упрощен. Технически объявление и определение могут быть в одном исходном файле. Но это тогда ограничивает использование класса другими классами.
Мартин Йорк,
11
на самом деле это не просто POD, это также должен быть тип int (int, short, bool, char ...)
Мэтт Кертис,
9
Обратите внимание, что это не просто вопрос инициализации значения: определенные таким образом константные целочисленные типы могут быть превращены реализацией в константы времени компиляции. Это не всегда то, что вы хотите, так как это увеличивает бинарную зависимость: клиентский код нуждается в перекомпиляции, если значение изменяется.
Стив Джессоп
5
@Martin: в дополнение к корректировке s / POD / целочисленный тип /, если адрес когда-либо берется, то также должно быть определение. Как это ни странно звучит, объявление с инициализатором в определении класса не является определением. Шаблонный Const идиома обеспечивает обходной путь для тех случаев , когда вам необходимо определение в файле заголовка. Другой и более простой обходной путь - это функция, которая выдает значение локальной статической константы. Приветствия и hth.,
Приветствия и hth. - Альф
3
Вы можете добавить пояснение, что int foo :: i = 0; не должно быть внутри функции (включая основную функцию). У меня было это в начале моей основной функции, и это не нравится.
qwerty9967
89

Для переменной :

foo.h:

class foo
{
private:
    static int i;
};

foo.cpp:

int foo::i = 0;

Это потому, что foo::iв вашей программе может быть только один экземпляр . Это своего рода эквивалент extern int iв заголовочном файле и int iв исходном файле.

Для константы вы можете поместить значение прямо в объявлении класса:

class foo
{
private:
    static int i;
    const static int a = 42;
};
Мэтт Кертис
источник
2
Это верный момент. Я добавлю это тоже мое объяснение. Но следует отметить, что это работает только для типов POD.
Мартин Йорк,
С тех пор, когда C ++ позволяет быть просто хорошим с объявлением в классе и без определения для целочисленных типов. С самого C ++ 98 или C ++ 03 или когда? Пожалуйста, поделитесь подлинными ссылками, пожалуйста. Стандартная формулировка C ++ не синхронизирована с компиляторами. Они упоминают, что член все еще должен быть определен, если они используются. Поэтому мне не нужно цитирование C ++ Standard
smRaj
1
Интересно, почему privateпеременные могут быть инициализированы вне Класса здесь, это может быть сделано для нестатических переменных также.
Кришна Оза
Вы нашли объяснение? @Krishna_Oza
nn0p
@ nn0p еще нет, но инициализация нестатических приватных переменных снаружи Classне имеет никакого смысла в Cpp.
Кришна Оза
42

Начиная с C ++ 17, статические члены могут быть определены в заголовке с ключевым словом inline .

http://en.cppreference.com/w/cpp/language/static

«Элемент статических данных может быть объявлен встроенным. Элемент статических данных может быть определен в определении класса и может указывать инициализатор элемента по умолчанию. Для него не требуется определение вне класса:»

struct X
{
    inline static int n = 1;
};
Умереть в сенте
источник
1
Это возможно начиная с C ++ 17, который в настоящее время становится новым стандартом.
Гребу
31

Для будущих читателей этого вопроса я хочу отметить, что вам следует избегать того, что предлагает monkey0506 .

Заголовочные файлы предназначены для объявлений.

Заголовочные файлы компилируются один раз для каждого .cppфайла, который прямо или косвенно #includesих использует, а код вне какой-либо функции запускается при инициализации программы раньше main().

Поместив: foo::i = VALUE;в заголовок, foo:iбудет присвоено значение VALUE(что бы это ни было) для каждого .cppфайла, и эти назначения будут выполняться в неопределенном порядке (определенном компоновщиком) перед main()запуском.

Что если мы #define VALUEбудем другим номером в одном из наших .cppфайлов? Он будет хорошо скомпилирован, и мы не сможем узнать, кто из них победит, пока не запустим программу.

Никогда не помещайте исполняемый код в заголовок по той же причине, по которой вы никогда не #includeиспользуете .cppфайл.

включают в себя охранники (которые, я согласен, вы всегда должны использовать) защищают вас от чего-то другого: один и тот же заголовок косвенно используется #includeнесколько раз при компиляции одного .cppфайла

Джошуа Клейтон
источник
2
Вы, конечно, правы в этом, за исключением случая с шаблоном класса (о котором не спрашивают, но я часто сталкиваюсь с этим). Поэтому, если класс полностью определен, а не шаблон класса, поместите эти статические члены в отдельный файл CPP, но для шаблонов классов определение должно быть в одной и той же единице перевода (например, в файле заголовка).
monkey0506
@ monkey_05_06: Кажется, это просто аргумент, позволяющий избежать статического члена в шаблонном коде: у вас уже есть один статический член для каждого экземпляра класса. проблема усугубляется возможной компиляцией заголовка в несколько файлов cpp ... Вы можете получить массу противоречивых определений.
Джошуа Клейтон
publib.boulder.ibm.com/infocenter/macxhelp/v6v81/… Эта ссылка изображает создание экземпляров статических элементов шаблона в основной функции, которая чище, хотя и немного обременительна.
Джошуа Клейтон
1
Ваш аргумент действительно огромен. Во-первых, вы не можете #define VALUE, потому что имя макроса не должно быть допустимым идентификатором. И даже если бы вы могли - кто бы это сделал? Заголовочные файлы для декларации -? Да ладно. Единственные случаи, когда вам следует избегать помещения значений в заголовок, это бороться с odr-used. А добавление значения в заголовок может привести к ненужной перекомпиляции всякий раз, когда вам нужно изменить значение.
Александр Фуляр,
20

С помощью компилятора Microsoft [1] статические переменные, которые не intпохожи на другие, также могут быть определены в заголовочном файле, но вне объявления класса, используя специфику Microsoft __declspec(selectany).

class A
{
    static B b;
}

__declspec(selectany) A::b;

Обратите внимание, что я не говорю, что это хорошо, я просто говорю, что это можно сделать.

[1] В наши дни больше компиляторов, чем MSC, __declspec(selectany)- по крайней мере, gcc и clang. Может быть, даже больше.

Иоганн Герелл
источник
17
int foo::i = 0; 

Правильный синтаксис для инициализации переменной, но он должен идти в исходном файле (.cpp), а не в заголовке.

Поскольку это статическая переменная, компилятору необходимо создать только одну ее копию. У вас должна быть строка «int foo: i», где-то в вашем коде, чтобы указать компилятору, куда ее поместить, в противном случае вы получите ошибку ссылки. Если это в заголовке, вы получите копию в каждом файле, который включает заголовок, поэтому получите многозначно определенные ошибки символов от компоновщика.

Дэвид Диббен
источник
12

У меня недостаточно репов здесь, чтобы добавить это в качестве комментария, но IMO - это хороший стиль, чтобы написать заголовки с защитой #include в любом случае, что, как заметил Paranaix несколько часов назад, предотвратило бы ошибку множественного определения. Если вы уже не используете отдельный файл CPP, нет необходимости использовать его только для инициализации статических нецелых элементов.

#ifndef FOO_H
#define FOO_H
#include "bar.h"

class foo
{
private:
    static bar i;
};

bar foo::i = VALUE;
#endif

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

monkey0506
источник
21
Защитники #include просто предотвращают использование нескольких определений на единицу перевода.
Пол Фульц II
3
по поводу хорошего стиля: вы должны добавить комментарий к заключительному концу:#endif // FOO_H
Рига
9
Это работает, только если у вас есть только один модуль компиляции, который включает foo.h. Если два или более cpps включают foo.h, что является типичной ситуацией, каждый cpp объявляет одну и ту же статическую переменную, поэтому компоновщик будет жаловаться с множественным определением `foo :: i ', если вы не используете компиляцию пакета с файлами (compile только один файл, который включает все cpps). Но хотя компиляция пакетов - это здорово, решение проблемы - объявить (int foo :: i = 0;) в cpp!
Алехадро Халабардер
1
Или просто использовать#pragma once
тамбре
12

Если вы хотите инициализировать некоторый составной тип (например, строку), вы можете сделать что-то вроде этого:

class SomeClass {
  static std::list<string> _list;

  public:
    static const std::list<string>& getList() {
      struct Initializer {
         Initializer() {
           // Here you may want to put mutex
           _list.push_back("FIRST");
           _list.push_back("SECOND");
           ....
         }
      }
      static Initializer ListInitializationGuard;
      return _list;
    }
};

Поскольку ListInitializationGuardстатическая переменная внутри SomeClass::getList()метода, она будет создана только один раз, что означает, что конструктор вызывается один раз. Это изменит initialize _listзначение, которое вам нужно. Любой последующий вызов getListпросто вернет уже инициализированный _listобъект.

Конечно, вы всегда должны обращаться к _listобъекту, вызывая getList()метод.

Крис Квятковски
источник
1
Вот версия этой идиомы, которая не требует создания одного метода для объекта-члена: stackoverflow.com/a/48337288/895245
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
9

Шаблон статического конструктора C ++ 11, который работает для нескольких объектов

Одна идиома была предложена по адресу: https://stackoverflow.com/a/27088552/895245, но здесь идет более чистая версия, которая не требует создания нового метода для каждого члена.

main.cpp

#include <cassert>
#include <vector>

// Normally on the .hpp file.
class MyClass {
public:
    static std::vector<int> v, v2;
    static struct StaticConstructor {
        StaticConstructor() {
            v.push_back(1);
            v.push_back(2);
            v2.push_back(3);
            v2.push_back(4);
        }
    } _staticConstructor;
};

// Normally on the .cpp file.
std::vector<int> MyClass::v;
std::vector<int> MyClass::v2;
// Must come after every static member.
MyClass::StaticConstructor MyClass::_staticConstructor;

int main() {
    assert(MyClass::v[0] == 1);
    assert(MyClass::v[1] == 2);
    assert(MyClass::v2[0] == 3);
    assert(MyClass::v2[1] == 4);
}

GitHub вверх по течению .

Скомпилируйте и запустите:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

Смотрите также: статические конструкторы в C ++? Мне нужно инициализировать частные статические объекты

Проверено на Ubuntu 19.04.

C ++ 17 встроенная переменная

Упоминается по адресу: https://stackoverflow.com/a/45062055/895245, но здесь приведен пример запуска нескольких файлов, чтобы сделать его еще более понятным: как работают встроенные переменные?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
источник
5

Вы также можете включить назначение в файл заголовка, если вы используете защиту заголовка. Я использовал эту технику для библиотеки C ++, которую я создал. Еще один способ добиться того же результата - использовать статические методы. Например...

class Foo
   {
   public:
     int GetMyStatic() const
     {
       return *MyStatic();
     }

   private:
     static int* MyStatic()
     {
       static int mStatic = 0;
       return &mStatic;
     }
   }

Приведенный выше код имеет «бонус», не требующий CPP / исходный файл. Опять же, метод, который я использую для моих библиотек C ++.


источник
4

Я следую за идеей от Карла. Мне нравится это, и теперь я использую это также. Я немного изменил обозначения и добавил некоторые функциональные возможности

#include <stdio.h>

class Foo
{
   public:

     int   GetMyStaticValue () const {  return MyStatic();  }
     int & GetMyStaticVar ()         {  return MyStatic();  }
     static bool isMyStatic (int & num) {  return & num == & MyStatic(); }

   private:

      static int & MyStatic ()
      {
         static int mStatic = 7;
         return mStatic;
      }
};

int main (int, char **)
{
   Foo obj;

   printf ("mystatic value %d\n", obj.GetMyStaticValue());
   obj.GetMyStaticVar () = 3;
   printf ("mystatic value %d\n", obj.GetMyStaticValue());

   int valMyS = obj.GetMyStaticVar ();
   int & iPtr1 = obj.GetMyStaticVar ();
   int & iPtr2 = valMyS;

   printf ("is my static %d %d\n", Foo::isMyStatic(iPtr1), Foo::isMyStatic(iPtr2));
}

это выводы

mystatic value 7
mystatic value 3
is my static 1 0
Алехадро Халабардер
источник
3

Также работает в файле privateStatic.cpp:

#include <iostream>

using namespace std;

class A
{
private:
  static int v;
};

int A::v = 10; // possible initializing

int main()
{
A a;
//cout << A::v << endl; // no access because of private scope
return 0;
}

// g++ privateStatic.cpp -o privateStatic && ./privateStatic
Эндрю
источник
3

Как насчет set_default()метода?

class foo
{
    public:
        static void set_default(int);
    private:
        static int i;
};

void foo::set_default(int x) {
    i = x;
}

Нам нужно будет только использовать set_default(int x)метод, и наша staticпеременная будет инициализирована.

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

Артуро Руис Маньяс
источник
3

Вероятно, возникла проблема с компоновщиком:

  • Предоставление определения класса и статического члена в заголовочном файле,
  • Включая этот заголовок в двух или более исходных файлах.

Это общая проблема для тех, кто начинает с C ++. Статический член класса должен быть инициализирован в одной единице перевода, т.е. в одном исходном файле.

К сожалению, статический член класса должен быть инициализирован вне тела класса. Это усложняет написание кода только для заголовка, и поэтому я использую совершенно другой подход. Вы можете предоставить свой статический объект через статическую или нестатическую функцию класса, например:

class Foo
{
    // int& getObjectInstance() const {
    static int& getObjectInstance() {
        static int object;
        return object;
    }

    void func() {
        int &object = getValueInstance();
        object += 5;
    }
};
никто особенный
источник
1
Я все еще полный N00B, насколько C ++ идет, но это выглядит блестяще для меня, большое спасибо! Я получаю идеальное управление жизненным циклом объекта-одиночки бесплатно.
Рафаэль Китовер
2

Один из "старых" способов определения констант состоит в замене их на enum:

class foo
{
    private:
        enum {i = 0}; // default type = int
        enum: int64_t {HUGE = 1000000000000}; // may specify another type
};

Этот способ не требует предоставления определения и избегает создания постоянного lvalue , что может избавить вас от некоторых головных болей, например, когда вы случайно используете его ODR .

anatolyg
источник
1

Я просто хотел упомянуть кое-что немного странное для меня, когда я впервые столкнулся с этим.

Мне нужно было инициализировать закрытый статический член данных в шаблонном классе.

в .h или .hpp это выглядит примерно так, чтобы инициализировать статический член данных класса шаблона:

template<typename T>
Type ClassName<T>::dataMemberName = initialValue;
Тайлер Хирс
источник
0

Это служит вашей цели?

//header file

struct MyStruct {
public:
    const std::unordered_map<std::string, uint32_t> str_to_int{
        { "a", 1 },
        { "b", 2 },
        ...
        { "z", 26 }
    };
    const std::unordered_map<int , std::string> int_to_str{
        { 1, "a" },
        { 2, "b" },
        ...
        { 26, "z" }
    };
    std::string some_string = "justanotherstring";  
    uint32_t some_int = 42;

    static MyStruct & Singleton() {
        static MyStruct instance;
        return instance;
    }
private:
    MyStruct() {};
};

//Usage in cpp file
int main(){
    std::cout<<MyStruct::Singleton().some_string<<std::endl;
    std::cout<<MyStruct::Singleton().some_int<<std::endl;
    return 0;
}
Дэвид Ногейра
источник