Как лучше всего инициализировать частный статический член данных в C++? Я пробовал это в своем заголовочном файле, но у меня возникают странные ошибки компоновщика:
class foo
{
private:
static int i;
};
int foo::i = 0;
Я предполагаю, что это потому, что я не могу инициализировать закрытый член извне класса. Итак, как лучше всего это сделать?
Приведенные ниже ответы не относятся к шаблонному классу. Говорят: инициализация должна идти в исходный файл. Для шаблонного класса это невозможно и не нужно.
C++ 17 допускает встроенную инициализацию статических элементов данных (даже для нецелочисленных типов): inline static int x[] = {1, 2, 3};. См. en.cppreference.com/w/cpp/language/static#Static_data_member s





Объявление класса должно быть в файле заголовка (или в исходном файле, если он не используется совместно). Файл: foo.h
class foo
{
private:
static int i;
};
Но инициализация должна быть в исходном файле. Файл: foo.cpp
int foo::i = 0;
Если инициализация находится в файле заголовка, тогда каждый файл, который включает файл заголовка, будет иметь определение статического члена. Таким образом, на этапе связывания вы получите ошибки компоновщика, поскольку код для инициализации переменной будет определен в нескольких исходных файлах.
Инициализация static int i должна выполняться вне каких-либо функций.
Примечание: Мэтт Кертис: указывает, что C++ допускает упрощение описанного выше, если статическая переменная-член имеет тип const int (например, int, bool, char). Затем вы можете объявить и инициализировать переменную-член непосредственно внутри объявления класса в файле заголовка:
class foo
{
private:
static int const i = 42;
};
Объявление класса может быть где угодно
да. Но я предполагаю, что вопрос был упрощен. Технически объявление и определение могут быть в одном исходном файле. Но это ограничивает использование класса другими классами.
Забыл сказать, что положил их в один файл. Это сработало как шарм. Спасибо!
на самом деле не только POD, это также должен быть тип int (int, short, bool, char ...)
Обратите внимание, что это не просто вопрос того, как инициализируется значение: определенные таким образом целочисленные типы const могут быть преобразованы реализацией в константы времени компиляции. Это не всегда то, что вам нужно, поскольку это увеличивает бинарную зависимость: клиентский код требует перекомпиляции, если значение изменяется.
Хотя, поскольку это личное, думаю, это не проблема. Извиняюсь.
он должен быть целым и всегда быть постоянным выражением (никогда не функцией)
@Martin: в дополнение к исправлению s / POD / интегрального типа /, если адрес когда-либо берется, также должно быть определение. Как это ни странно звучит, объявление с инициализатором в определении класса не является определением. шаблонная идиома const предоставляет обходной путь для случаев, когда вам нужно определение в файле заголовка. Другой и более простой обходной путь - это функция, которая производит значение локальной статической константы. Ура & hth.,
Может ли инициализация быть помещена в заголовок, если вы используете наблюдатели заголовков, вызывающие включение заголовка только один раз?
@Paranaix: 1) Вы имеете в виду Header Guards. 2) Как заметил Мэтт Кертис: только специальный случай const int / char / bool (см. Последний абзац выше)
Вы можете добавить пояснение, что int foo :: i = 0; не должен находиться внутри функции (включая основную функцию). У меня это было в начале моей основной функции, и мне это не нравится.
@MattCurtis "или любого другого типа, если вы используете C++ 11". Хотя стоит добавить только я понимаю, если дополнительно указано как constexpr для нецелых типов.
@wards, можно ли использовать constexpr с нецелочисленными типами?
@ sasha.sochka Может вернуть любой en.cppreference.com/w/cpp/concept/LiteralType
@ sasha.sochka не с типами, у которых есть деструкторы не по умолчанию.
Я был неправильно процитирован в этом ответе, его содержание принадлежит его редакторам, а не мне. Я, конечно, не сказал «любой константный тип» или «любой тип в C++ 11».
@LokiAstari Все в порядке - спасибо за редактирование. Я только что заметил в некоторых ответах на SO, иногда сбивает с толку, кто что говорит. (Также иногда мои ответы были отредактированы так, как я думаю, меняет смысл. Думаю, это цена геймификации!)
На самом деле в файле CPP достаточно определение (вместо инициализации, которая не является обязательной): int foo::i;
Вы упомянули, что это может быть в источнике, если не передано. Будет ли за / против делать это, если значение "Private Static", так как это не может быть разделено, и, по моему мнению, это упрощает запись, но я не знаю, отрицательно ли это делать это иначе, чем для будущего использования, если вы планируете использовать его, вы не можете просто сделать его общедоступным, тогда вам придется объявить его в заголовке.
Что, если это не элементарный тип данных?
@DustinK Первый метод, показанный выше, работает для всех типов. Второй прием - это оптимизация для целочисленных типов.
Для Переменная:
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;
};
Это верный момент. Я тоже добавлю это свое объяснение. Но следует отметить, что это работает только для типов POD.
С тех пор, как C++ позволяет просто хорошо работать с объявлением в классе и без определения интегральных типов. С самого C++ 98 или C++ 03 или когда? Пожалуйста, поделитесь аутентичными ссылками. Стандартные формулировки C++ не синхронизированы с компиляторами. Они упоминают, что член должен быть определен, если они используются. Итак, мне не нужно цитирование стандарта C++, хотя
Интересно, почему переменные private могут быть инициализированы вне класса здесь, можно ли это сделать и для нестатических переменных.
Вы нашли объяснение? @Krishna_Oza
@ nn0p еще нет, но инициализация нестатических частных переменных вне Class не имеет смысла в Cpp.
Возникла проблема с инициализацией статической переменной const в определении класса. Если вы поместите это определение класса в файл .h или .hpp и включите его в несколько файлов .cpp, вы можете получить предупреждения о "неиспользуемой переменной" во время компиляции. Статические переменные лучше инициализировать в .cpp файле классов.
int foo::i = 0;
Это правильный синтаксис для инициализации переменной, но он должен находиться в исходном файле (.cpp), а не в заголовке.
Поскольку это статическая переменная, компилятору необходимо создать только одну ее копию. У вас должна быть строка "int foo: i" где-то в вашем коде, чтобы сообщить компилятору, куда ее поместить, иначе вы получите ошибку ссылки. Если это находится в заголовке, вы получите копию в каждом файле, который включает заголовок, поэтому получите многократно определенные ошибки символов от компоновщика.
С компилятором Microsoft [1] статические переменные, которые не похожи на int, также могут быть определены в файле заголовка, но вне объявления класса, с использованием специфического для Microsoft __declspec(selectany).
class A
{
static B b;
}
__declspec(selectany) A::b;
Заметьте, я не говорю, что это хорошо, я просто говорю, что это можно сделать.
[1] В наши дни __declspec(selectany) поддерживают больше компиляторов, чем MSC - по крайней мере, gcc и clang. Может даже больше.
У меня недостаточно репутации, чтобы добавить это в качестве комментария, но ИМО, в любом случае, хороший стиль - писать заголовки с помощью # включить охранников, что, как отметил Паранаикс несколько часов назад, предотвратит ошибку множественного определения. Если вы уже не используете отдельный файл CPP, нет необходимости использовать его только для инициализации статических нецелых членов.
#ifndef FOO_H
#define FOO_H
#include "bar.h"
class foo
{
private:
static bar i;
};
bar foo::i = VALUE;
#endif
Я не вижу необходимости использовать для этого отдельный файл CPP. Конечно, можете, но нет никаких технических причин, по которым вам это следовало бы.
#include guards просто предотвращает использование нескольких определений на единицу перевода.
по поводу хорошего стиля: вы должны добавить комментарий в конце, если: #endif // FOO_H
Это работает, только если у вас есть только один модуль компиляции, который включает foo.h. Если два или более cpps включают foo.h, что является типичной ситуацией, каждый cpp объявляет одну и ту же статическую переменную, поэтому компоновщик будет жаловаться на несколько определений `foo :: i ', если вы не используете компиляцию пакета с файлами (compile только один файл, включающий все cpps). Но хотя компиляция пакетов - это замечательно, решение проблемы - объявить (int foo :: i = 0;) в cpp!
Или просто используйте #pragma once
Для будущих тех, кто рассматривает этот вопрос, я хочу указать, что вам следует избегать того, что monkey0506 предлагает.
Заголовочные файлы предназначены для объявлений.
Заголовочные файлы компилируются один раз для каждого файла .cpp, который прямо или косвенно #includes их, и код вне какой-либо функции запускается при инициализации программы перед main().
Если поместить: foo::i = VALUE; в заголовок, foo:i будет присвоено значение VALUE (что бы оно ни было) для каждого файла .cpp, и эти назначения будут происходить в неопределенном порядке (определяемом компоновщиком) перед запуском main().
Что, если #define VALUE будет другим номером в одном из наших файлов .cpp? Он будет компилироваться нормально, и мы не сможем узнать, какой из них выиграет, пока мы не запустим программу.
Никогда не помещайте исполняемый код в заголовок по той же причине, по которой вы никогда не помещаете #include в файл .cpp.
включить охранники (которые, я согласен, вы всегда должны использовать) защищают вас от чего-то другого: один и тот же заголовок косвенно используется #included несколько раз при компиляции одного файла .cpp
Вы, конечно, правы насчет этого, за исключением случая с шаблоном класса (о котором не спрашивают, но я имею дело с большим количеством). Поэтому, если класс полностью определен, а не шаблон класса, поместите эти статические члены в отдельный файл CPP, но для шаблонов классов определение должно быть в той же единице перевода (например, в файле заголовка).
@ monkey_05_06: Кажется, это аргумент, позволяющий избежать статического члена в шаблонном коде: у вас уже есть один статический член для каждого экземпляра класса. проблема усугубляется возможной компиляцией заголовка в несколько файлов cpp ... Вы можете получить множество противоречащих друг другу определений.
Ваш аргумент действительно огромен. Во-первых, вы не можете #define VALUE, потому что имя макроса не является допустимым идентификатором. И даже если бы вы могли - кто бы это сделал? Заголовочные файлы предназначены для объявления -? Да ладно .. Единственные случаи, когда вам следует избегать размещения значений в заголовке, - это бороться с odr-used. А размещение значения в заголовке может привести к ненужной перекомпиляции всякий раз, когда вам нужно изменить значение.
Вы также можете включить назначение в файл заголовка, если используете защиту заголовка. Я использовал эту технику для созданной мной библиотеки 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 будет инициализирована.
Это не будет противоречить остальным комментариям, на самом деле он следует тому же принципу инициализации переменной в глобальной области видимости, но с помощью этого метода мы делаем его явным (и легким для просмотра и понимания) вместо определения переменной висит там.
Если вы хотите инициализировать какой-либо составной тип (например, строку), вы можете сделать что-то вроде этого:
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().
Вот версия этой идиомы, которая не требует создания одного метода для каждого объекта-члена: stackoverflow.com/a/48337288/895245
Это служит вашей цели?
//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;
}
Я просто хотел упомянуть кое-что немного странное для меня, когда впервые столкнулся с этим.
Мне нужно было инициализировать частный статический член данных в классе шаблона.
в .h или .hpp это выглядит примерно так, чтобы инициализировать статический член данных класса шаблона:
template<typename T>
Type ClassName<T>::dataMemberName = initialValue;
Начиная с C++ 17, статические члены могут быть определены в заголовке с помощью ключевого слова в соответствии.
http://en.cppreference.com/w/cpp/language/static
«Статический член данных может быть объявлен встроенным. Встроенный статический член данных может быть определен в определении класса и может указывать инициализатор члена по умолчанию. Он не требует внеклассного определения:»
struct X
{
inline static int n = 1;
};
Это возможно, начиная с C++ 17, который в настоящее время становится новым стандартом.
Проблема компоновщика, с которой вы столкнулись, вероятно, вызвана:
Это обычная проблема для тех, кто начинает с C++. Статический член класса должен быть инициализирован в одной единице перевода, то есть в одном исходном файле.
К сожалению, статический член класса должен быть инициализирован вне тела класса. Это усложняет написание кода только для заголовков, поэтому я использую совершенно другой подход. Вы можете предоставить свой статический объект с помощью статической или нестатической функции класса, например:
class Foo
{
// int& getObjectInstance() const {
static int& getObjectInstance() {
static int object;
return object;
}
void func() {
int &object = getValueInstance();
object += 5;
}
};
Я все еще полный новичок в том, что касается C++, но мне это кажется блестящим, большое вам спасибо! Я получаю идеальное управление жизненным циклом одноэлементного объекта бесплатно.
Шаблон статического конструктора C++ 11, который работает для нескольких объектов
Одна идиома была предложена в: https://stackoverflow.com/a/27088552/895245, но здесь идет более чистая версия, которая не требует создания нового метода для каждого члена.
main.cpp
#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);
}
Скомпилируйте и запустите:
g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out
См. Также: статические конструкторы в C++? Мне нужно инициализировать частные статические объекты
Проверено на Ubuntu 19.04.
Встроенная переменная C++ 17
Упоминается по адресу: https://stackoverflow.com/a/45062055/895245, но вот многофайловый исполняемый пример, чтобы сделать его еще яснее: Как работают встроенные переменные?
Один из "старых" способов определения констант - заменить их на enum:
class foo
{
private:
enum {i = 0}; // default type = int
enum: int64_t {HUGE = 1000000000000}; // may specify another type
};
Этот способ не требует определения и позволяет избежать создания константы lvalue, что может избавить вас от некоторых головных болей, например когда вы случайно ODR-использование это.
Привет, Джейсон. Я не нашел комментария к инициализации статических членов по умолчанию (особенно интегральных). На самом деле вам нужно написать int foo :: i, чтобы компоновщик мог его найти, но он будет автоматически инициализирован 0! Этой строки будет достаточно: int foo :: i; (Это справедливо для всех объектов, хранящихся в статической памяти, компоновщик отвечает за инициализацию статических объектов.)