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

435

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

class foo
{
    private:
        static int i;
};

int foo::i = 0;

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

  • 2
    Привет Джейсон. Я не нашел комментария по умолчанию для инициализации статических элементов (особенно интегральных). Фактически вам нужно написать int foo :: i, чтобы компоновщик мог его найти, но он будет автоматически инициализирован с 0! Этой строки будет достаточно: int foo :: i; (Это действительно для всех объектов, хранящихся в статической памяти, компоновщик отвечает за инициализацию статических объектов.)
  • 1
    Ответы ниже не относятся к шаблону класса. Они говорят: инициализация должна идти в исходный файл. Для шаблонного класса это ни невозможно, ни необходимо.
Показать ещё 1 комментарий
Теги:
initialization
static-members

17 ответов

472
Лучший ответ

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

class foo
{
    private:
        static int i;
};

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

int foo::i = 0;

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

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

class foo
{
    private:
        static int const i = 42;
};
  • 1
    Объявление класса может быть где угодно
  • 4
    Да. Но я предполагаю, что вопрос был упрощен. Технически объявление и определение могут быть в одном исходном файле. Но это тогда ограничивает использование класса другими классами.
Показать ещё 18 комментариев
77

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

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.
  • 0
    С тех пор, когда C ++ позволяет быть просто хорошим с объявлением в классе и без определения для целочисленных типов. С самого C ++ 98 или C ++ 03 или когда? Пожалуйста, поделитесь подлинными ссылками, пожалуйста. Стандартная формулировка C ++ не синхронизирована с компиляторами. Они упоминают, что член все еще должен быть определен, если они используются. Итак, мне не нужно цитирование C ++ Standard, хотя
Показать ещё 4 комментария
24

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

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

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

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

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

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

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

  • 2
    Вы, конечно, правы в этом, за исключением случая с шаблоном класса (о котором не спрашивают, но я часто сталкиваюсь с этим). Таким образом, если класс полностью определен, а не шаблон класса, поместите эти статические члены в отдельный файл CPP, но для шаблонов классов определение должно быть в одной и той же единице перевода (например, в файле заголовка).
  • 0
    @ monkey_05_06: Кажется, это просто аргумент, позволяющий избежать статического члена в шаблонном коде: у вас уже есть один статический член для каждого экземпляра класса. проблема усугубляется возможной компиляцией заголовка в несколько файлов cpp ... Вы можете получить массу противоречивых определений.
Показать ещё 2 комментария
19

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

class A
{
    static B b;
}

__declspec(selectany) A::b;

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

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

16
int foo::i = 0; 

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

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

13

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

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

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

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

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

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

class foo
{
private:
    static bar i;
};

bar foo::i = VALUE;
#endif

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

  • 20
    Защитники #include просто предотвращают использование нескольких определений на единицу перевода.
  • 3
    относительно хорошего стиля: вы должны добавить комментарий к завершающему endif: #endif // FOO_H
Показать ещё 2 комментария
10

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

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().

  • 0
    Вот версия этой идиомы, которая не требует создания одного метода для объекта-члена: stackoverflow.com/a/48337288/895245
5

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

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

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

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

4

Статический шаблон конструктора, который работает для нескольких объектов

Одна идиома была предложена по адресу: https://stackoverflow.com/questions/185844/how-to-initialize-private-static-members-in-c но здесь идет более чистая версия, которая не требует создания нового метода для каждого члена и пример выполнения:

#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 вверх по течению.

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

Протестировано с помощью g++ -std=C++11 -Wall -Wextra, GCC 7.3, Ubuntu 18.04.

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

Как насчет метода 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

Также работает в файле 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
2

Проблема с компоновщиком, с которой вы столкнулись, вероятно, вызвана:

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

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

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

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

    void func() {
        int &object = getValueInstance();
        object += 5;
    }
};
1

Один способ "старой школы" определить константы - заменить их enum:

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

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню