6

Как инициализировать приватные статические члены данных в заголовочном файле

1

Проблема: Инициализация статического приватного члена данных в C++

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

class foo
{
    private:
        static int i;
};

int foo::i = 0;

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

5 ответ(ов)

1

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

Согласно документации, "Статический член данных может быть объявлен как inline. Inline-статический член данных может быть определён в определении класса и может указывать на значение по умолчанию для инициализации члена. Нет необходимости в определении вне класса."

Вот пример:

struct X
{
    inline static int n = 1;
};

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

1

В вашем вопросе речь идет о статических членах класса в C++. Рассмотрим оба случая - для переменной и для константы.

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

// foo.h
class foo
{
private:
    static int i; // Объявление статической переменной i
};
// foo.cpp
int foo::i = 0; // Определение и инициализация статической переменной i

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

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

class foo
{
private:
    static int i; // Статическая переменная
    const static int a = 42; // Константа, значение которой задается сразу
};

Стоит отметить, что для статических член-данных класса (как переменные, так и константы) необходимо помнить о особенностях инициализации, чтобы избежать ошибок при компиляции.

0

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

class A
{
    static B b;
}

__declspec(selectany) A::b;

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

В последние годы более чем только компиляторы Microsoft поддерживают __declspec(selectany) - как минимум, это также поддерживают gcc и clang. Возможно, даже больше компиляторов.

0

Да, синтаксис int foo::i = 0; является правильным для инициализации статической переменной, но эта инициализация должна производиться в исходном файле (.cpp), а не в заголовочном файле (.h).

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

0

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

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

  public:
    static const std::list<std::string>& getList() {
      struct Initializer {
         Initializer() {
           // Здесь вы можете установить мьютекс
           _list.push_back("FIRST");
           _list.push_back("SECOND");
           // ...
         }
      };
      static Initializer ListInitializationGuard;
      return _list;
    }
};

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

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь