Как инициализировать частные статические члены В C++?
что является лучшим способом, чтобы инициализировать собственный статический член данных в C++? Я попробовал это в моем заголовочном файле, но это дает мне странные ошибки компоновщика:
class foo
{
private:
static int i;
};
int foo::i = 0;
Я предполагаю, что это потому, что я не могу инициализировать собственный член из-за пределов класса. Так как же лучше всего это сделать?
17 ответов:
объявление класса должно быть в заголовочном файле (или в исходном файле, если не общая).
Файл: foo.hclass foo { private: static int i; };но инициализация должна быть в исходном файле.
Файл: foo.cppint foo::i = 0;если инициализация находится в файле заголовка, то каждый файл, который включает в себя файл заголовка будет иметь определение статического члена. Таким образом, на этапе компоновки вы получите ошибки компоновщика, так как код для инициализации переменной будет определен в несколько исходных файлов.
Примечание: Мэтт Кертис: указывает, что C++ позволяет упростить вышеизложенное, если статическая переменная-член имеет тип const int (например,
int,bool,char). Затем можно объявить и инициализировать переменную-член непосредственно в объявлении класса в заголовочном файле:class foo { private: static int const i = 42; };
на переменная:
фу.h:
class foo { private: static int i; };фу.cpp:
int foo::i = 0;это потому, что может быть только один экземпляр
foo::iв вашей программе. Это своего рода эквивалентextern int iв заголовочном файле иint iв исходном файле.на постоянный вы можете поместить значение прямо в объявление класса:
class foo { private: static int i; const static int a = 42; };
для будущих зрителей этого вопроса, я хочу отметить, что вы должны избежать того, что monkey0506 предлагает.
заголовочные файлы предназначены для объявлений.
заголовочные файлы компилируются один раз для каждого
.cppфайл, который прямо или косвенно#includesих, и код вне любой функции запускается при инициализации программы, передmain().положить:
foo::i = VALUE;в заголовкеfoo:iбудет присвоено значениеVALUE(что бы это ни было) для каждого.cppфайл, и эти назначения будут происходить в неопределенном порядке (определяемом компоновщиком) передmain()запускается.что если мы
#define VALUEчтобы быть другим числом в одном из наших.cppфайлов? Он будет компилироваться нормально, и у нас не будет способа узнать, какой из них выиграет, пока мы не запустим программу.никогда не помещайте выполненный код в заголовок по той же причине, что вы никогда
#includea .включить охранников (который я согласен, что вы всегда должны использовать) защитить вас от чего-то другого: тот же заголовок косвенно
#included несколько раз при компиляции одного.cppfile
С компилятором Microsoft[1], статические переменные, которые не являются
int- like также может быть определен в заголовочном файле, но вне объявления класса, используя Microsoft specific__declspec(selectany).class A { static B b; } __declspec(selectany) A::b;обратите внимание, что я не говорю, что это хорошо, я просто говорю, что это можно сделать.
[1] в эти дни больше компиляторов, чем поддержка MSC
__declspec(selectany)- по крайней мере gcc и лязг. Может быть, даже больше.
int foo::i = 0;является правильным синтаксисом для инициализации переменной, но он должен идти в исходном файле (.СРР), а не в заголовке.
поскольку это статическая переменная, компилятору необходимо создать только одну ее копию. У вас должна быть строка "int foo:i" где-то в вашем коде, чтобы сообщить компилятору, куда его поместить, иначе вы получите ошибку ссылки. Если это находится в заголовке, вы получите копию в каждом файле, который включает заголовок, поэтому получите умноженные определенные ошибки символов из компоновщик.
У меня нет достаточно репутации здесь, чтобы добавить это в качестве комментария, но ИМО это хороший стиль, чтобы написать свои заголовки с #включить охранников в любом случае, что, как отметил Paranaix несколько часов назад, предотвратило бы ошибку с несколькими определениями. Если вы уже не используете отдельный CPP-файл, нет необходимости использовать его только для инициализации статических нецелочисленных членов.
#ifndef FOO_H #define FOO_H #include "bar.h" class foo { private: static bar i; }; bar foo::i = VALUE; #endifЯ не вижу необходимости использовать для этого отдельный CPP-файл. Конечно, можно, но нет никаких технических причин, почему ты должен был это сделать.
если вы хотите инициализировать какой-то составной тип (например, строку), вы можете сделать что-то вроде этого:
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просто уже инициализирован
начиная с C++17, статические члены могут быть определены в заголовке inline ключевое слово.
http://en.cppreference.com/w/cpp/language/static
" статический элемент данных может быть объявлен встроенным. Встроенный статический элемент данных может быть определен в определении класса и может указывать инициализатор элемента по умолчанию. Ему не нужно внеклассовое определение:"
struct X { inline static int n = 1; };
вы также можете включить назначение в файл заголовка, если вы используете защиту заголовка. Я использовал этот метод для библиотеки 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
также работает в 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
о
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переменная будет инициализирована.это не будет противоречить остальным комментариям, на самом деле он следует тому же принципу инициализации переменной в глобальной области, но с помощью этого метода мы делаем его явным (и легко увидеть-понять) вместо того, чтобы определение переменной висит там.
статический шаблон конструктора, который работает для нескольких объектов
одна идиома была предложена по адресу:https://stackoverflow.com/a/27088552/895245 но здесь идет более чистая версия, которая не требует создания нового метода для каждого члена, и запускаемый пример:
#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.
Я просто хотел сказать, что немного странно для меня, когда я впервые столкнулся с этим.
Мне нужно было инициализировать частный статический элемент данных в классе шаблона.
в рамках .ч или .hpp, это выглядит примерно так, чтобы инициализировать статический член данных класса шаблона:
template<typename T> Type ClassName<T>::dataMemberName = initialValue;
проблема компоновщика, с которой вы столкнулись, вероятно, вызвана:
- предоставление определения класса и статического члена в заголовочном файле,
- включая этот заголовок в два или более исходных файлов.
Это общая проблема для тех, кто начинает с C++. Статический член класса должен быть инициализирован в одной единице перевода, т. е. в одном исходном файле.
к сожалению, статический член класса должен быть инициализирован вне тело класса. Это усложняет написание только заголовка кода, и, следовательно, я использую совершенно другой подход. Вы можете предоставить свой статический объект через статическую или нестатическую функцию класса, например:
class Foo { // int& getObjectInstance() const { static int& getObjectInstance() { static int object; return object; } void func() { int &object = getValueInstance(); object += 5; } };
это служит вашей цели?
//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; }
один "олдскульный" способ определения констант-заменить их на
enum:class foo { private: enum {i = 0}; // default type = int enum: int64_t {HUGE = 1000000000000}; // may specify another type };этот способ не требует определения, и избежать постоянных lvalue, что может сэкономить вам некоторые головные боли, например, когда вы случайно ODR-use его.
Comments