Как инициализировать приватные статические члены данных в заголовочном файле
Проблема: Инициализация статического приватного члена данных в C++
Как лучший способ инициализировать приватный статический член данных в C++? Я попробовал сделать это в своем заголовочном файле, но получил странные ошибки компоновщика:
class foo
{
private:
static int i;
};
int foo::i = 0;
Я предполагаю, что это происходит из-за того, что я не могу инициализировать приватный член из вне класса. Какой лучший способ решить эту проблему?
5 ответ(ов)
С начала C++17 статические члены могут быть определены в заголовочном файле с использованием ключевого слова inline.
Согласно документации, "Статический член данных может быть объявлен как inline. Inline-статический член данных может быть определён в определении класса и может указывать на значение по умолчанию для инициализации члена. Нет необходимости в определении вне класса."
Вот пример:
struct X
{
inline static int n = 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; // Константа, значение которой задается сразу
};
Стоит отметить, что для статических член-данных класса (как переменные, так и константы) необходимо помнить о особенностях инициализации, чтобы избежать ошибок при компиляции.
С компиляторами Microsoft статические переменные, которые не являются типами, подобными int
, также могут быть определены в заголовочном файле, но вне объявления класса, с использованием специфичного для Microsoft модификатора __declspec(selectany)
.
class A
{
static B b;
}
__declspec(selectany) A::b;
Обратите внимание, что я не утверждаю, что это хорошая практика, я просто говорю, что это возможно.
В последние годы более чем только компиляторы Microsoft поддерживают __declspec(selectany)
- как минимум, это также поддерживают gcc и clang. Возможно, даже больше компиляторов.
Да, синтаксис int foo::i = 0;
является правильным для инициализации статической переменной, но эта инициализация должна производиться в исходном файле (.cpp), а не в заголовочном файле (.h).
Поскольку это статическая переменная, компилятору нужно создать только одну копию этой переменной. Вам необходимо иметь строку int foo::i;
где-то в вашем коде, чтобы указать компилятору, где разместить переменную. Если вы разместите ее в заголовочном файле, то в каждом файле, который подключает этот заголовок, создастся своя копия переменной, что приведет к ошибкам множественного определения символов при компоновке.
Если вы хотите инициализировать некоторый составной тип (например, строку), вы можете сделать что-то вроде этого:
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()
.
В чем разница между #include <filename> и #include "filename"?
Имеют ли круглые скобки после имени типа значение при использовании new?
Каковы преимущества инициализации списка (с использованием фигурных скобок)?
Как проще всего инициализировать std::vector с жестко заданными элементами?
Что такое Правило трёх?