Как инициализировать частные статические члены в C++?
Каков наилучший способ инициализации частного статического члена данных в C++? Я попробовал это в моем заголовочном файле, но он дает мне странные ошибки компоновщика:
class foo
{
private:
static int i;
};
int foo::i = 0;
Я предполагаю, что это потому, что я не могу инициализировать частного члена вне класса. Так каков лучший способ сделать это?
18 ответов
Объявление класса должно быть в заголовочном файле (или в исходном файле, если он не используется совместно).
Файл: foo.h
class foo
{
private:
static int i;
};
Но инициализация должна быть в исходном файле.
Файл: foo.cpp
int foo::i = 0;
Если инициализация находится в файле заголовка, то каждый файл, который включает файл заголовка, будет иметь определение статического члена. Таким образом, на этапе компоновки вы получите ошибки компоновщика, так как код для инициализации переменной будет определен в нескольких исходных файлах.
Примечание: Мэтт Кертис: указывает, что C++ допускает упрощение вышеупомянутого, если статическая переменная-член имеет тип const int (например, int
, bool
, char
). Затем вы можете объявить и инициализировать переменную-член непосредственно внутри объявления класса в заголовочном файле:
class foo
{
private:
static int const i = 42;
};
Для переменной:
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;
};
Начиная с C++17, статические члены могут быть определены в заголовке с ключевым словом inline.
http://en.cppreference.com/w/cpp/language/static
"Элемент статических данных может быть объявлен встроенным. Элемент статических данных может быть определен в определении класса и может указывать инициализатор элемента по умолчанию. Для него не требуется определение вне класса:"
struct X
{
inline static int n = 1;
};
Для будущих читателей этого вопроса я хочу отметить, что вам следует избегать того, что предлагает monkey0506.
Заголовочные файлы для объявлений.
Заголовочные файлы компилируются один раз для каждого .cpp
файл, который прямо или косвенно #includes
их, и код вне любой функции запускается при инициализации программы, до main()
,
Поместив: foo::i = VALUE;
в заголовок, foo:i
будет назначено значение VALUE
(что бы это ни было) для каждого .cpp
файл, и эти назначения будут происходить в неопределенном порядке (определяется компоновщиком) до main()
это запустить.
Что если мы #define VALUE
быть другим числом в одном из наших .cpp
файлы? Он будет хорошо скомпилирован, и мы не сможем узнать, кто из них победит, пока не запустим программу.
Никогда не помещайте исполняемый код в заголовок по той же причине, по которой вы никогда #include
.cpp
файл.
включают в себя охранников (которые я согласен, вы всегда должны использовать) защищают вас от чего-то другого: тот же заголовок косвенно #include
D несколько раз при составлении одного .cpp
файл
С компилятором Microsoft [1], статические переменные, которые не являются int
-like также может быть определен в заголовочном файле, но вне объявления класса, с использованием Microsoft __declspec(selectany)
,
class A
{
static B b;
}
__declspec(selectany) A::b;
Обратите внимание, что я не говорю, что это хорошо, я просто говорю, что это можно сделать.
[1] В наши дни больше компиляторов, чем MSC __declspec(selectany)
- по крайней мере, GCC и лязг. Может быть, даже больше.
int foo::i = 0;
Правильный синтаксис для инициализации переменной, но он должен идти в исходном файле (.cpp), а не в заголовке.
Поскольку это статическая переменная, компилятору необходимо создать только одну ее копию. У вас должна быть строка "int foo: i", где-то в вашем коде, чтобы указать компилятору, куда ее поместить, в противном случае вы получите ошибку ссылки. Если он находится в заголовке, вы получите копию в каждом файле, который включает заголовок, поэтому получите многозначно определенные ошибки символов от компоновщика.
Статический шаблон конструктора, который работает для нескольких объектов
Одна идиома была предложена по адресу: /questions/26969726/kak-initsializirovat-chastnyie-staticheskie-chlenyi-v-c/26969736#26969736 но здесь идет более чистая версия, не требующая создания нового метода для каждого члена, и пример запуска:
#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);
}
Смотрите также: статические конструкторы в C++? Мне нужно инициализировать частные статические объекты
Протестировано с g++ -std=c++11 -Wall -Wextra
, GCC 7.2, Ubuntu 17.10.
Если вы хотите инициализировать некоторый составной тип (например, строку), вы можете сделать что-то вроде этого:
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()
метод.
У меня недостаточно репов здесь, чтобы добавить это в качестве комментария, но 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 для этого. Конечно, вы можете, но нет никаких технических причин, почему вы должны это делать.
Вероятно, возникла проблема с компоновщиком:
- Предоставление определения класса и статического члена в заголовочном файле,
- Включая этот заголовок в двух или более исходных файлах.
Это общая проблема для тех, кто начинает с C++. Статический член класса должен быть инициализирован в одной единице перевода, то есть в одном исходном файле.
К сожалению, статический член класса должен быть инициализирован вне тела класса. Это усложняет написание кода только для заголовка, и поэтому я использую совершенно другой подход. Вы можете предоставить свой статический объект через статическую или нестатическую функцию класса, например:
class Foo
{
// int& getObjectInstance() const {
static int& getObjectInstance() {
static int object;
return object;
}
void func() {
int &object = getValueInstance();
object += 5;
}
};
Вы также можете включить назначение в файл заголовка, если вы используете защиту заголовка. Я использовал эту технику для библиотеки C++, которую я создал. Другой способ достичь того же результата - использовать статические методы. Например...
class Foo
{
public:
int GetMyStatic() const
{
return *MyStatic();
}
private:
static int* MyStatic()
{
static int mStatic = 0;
return &mStatic;
}
}
Приведенный выше код имеет "бонус", не требующий CPP/ исходный файл. Опять же, метод, который я использую для моих библиотек C++.
Я следую за идеей от Карла. Мне это нравится, и теперь я тоже им пользуюсь. Я немного изменил обозначения и добавил некоторые функциональные возможности
#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
Как насчет 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
переменная будет инициализирована.
Это не будет противоречить остальным комментариям, фактически оно следует тому же принципу инициализации переменной в глобальной области видимости, но с помощью этого метода мы делаем его явным (и легко видимым для понимания) вместо того, чтобы иметь определение переменной висит там.
Также работает в файле 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
Один из "старых" способов определения констант состоит в том, чтобы заменить их enum
:
class foo
{
private:
enum {i = 0}; // default type = int
enum: int64_t {HUGE = 1000000000000}; // may specify another type
};
Этот способ не требует предоставления определения и избегает создания постоянного lvalue, что может избавить вас от некоторых головных болей, например, когда вы случайно используете его в ODR.
Вот все возможности и ошибки в одном простом примере ...
#ifndef Foo_h
#define Foo_h
class Foo
{
static const int a = 42; // OK
static const int b {7}; // OK
//static int x = 42; // ISO C++ forbids in-class initialization of non-const static member 'Foo::x'
//static int y {7}; // ISO C++ forbids in-class initialization of non-const static member 'Foo::x'
static int x;
static int y;
int m = 42;
int n {7};
};
// Foo::x = 42; // error: 'int Foo::x' is private
int Foo::x = 42; // OK in Foo.h if included in only one *.cpp -> *.o file!
int Foo::y {7}; // OK
// int Foo::y {7}; // error: redefinition of 'int Foo::y'
// ONLY if the compiler can see both declarations at the same time it,
// OTHERWISE you get a linker error
#endif // Foo_h
Но лучше поместить это в Foo.cpp. Таким образом, вы можете отдельно скомпилировать каждый файл и связать их позже, иначе Foo: x будет присутствовать в нескольких объектных файлах и вызовет ошибку компоновщика. ...
// Foo::x = 42; // error: 'int Foo::x' is private, bad if Foo::X is public!
int Foo::x = 42; // OK in Foo.h if included in only one *.cpp -> *.o file!
int Foo::y {7}; // OK
Я просто хотел упомянуть кое-что немного странное для меня, когда я впервые столкнулся с этим.
Мне нужно было инициализировать приватный статический член данных в шаблонном классе.
в.h или.hpp это выглядит примерно так, чтобы инициализировать статический член данных класса шаблона:
template<typename T>
Type ClassName<T>::dataMemberName = initialValue;
Это служит вашей цели?
//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;
}