Как инициализировать частные статические члены в C++?

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

class foo
{
    private:
        static int i;
};

int foo::i = 0;

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

Привет, Джейсон. Я не нашел комментария к инициализации статических членов по умолчанию (особенно интегральных). На самом деле вам нужно написать int foo :: i, чтобы компоновщик мог его найти, но он будет автоматически инициализирован 0! Этой строки будет достаточно: int foo :: i; (Это справедливо для всех объектов, хранящихся в статической памяти, компоновщик отвечает за инициализацию статических объектов.)

Nico 18.07.2012 13:59

Приведенные ниже ответы не относятся к шаблонному классу. Говорят: инициализация должна идти в исходный файл. Для шаблонного класса это невозможно и не нужно.

Joachim W 09.06.2016 18:20

C++ 17 допускает встроенную инициализацию статических элементов данных (даже для нецелочисленных типов): inline static int x[] = {1, 2, 3};. См. en.cppreference.com/w/cpp/language/static#Static_data_member‌ s

Vladimir Reshetnikov 15.02.2018 00:59
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
546
3
542 600
17
Перейти к ответу Данный вопрос помечен как решенный

Ответы 17

Ответ принят как подходящий

Объявление класса должно быть в файле заголовка (или в исходном файле, если он не используется совместно). Файл: 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;
};

Объявление класса может быть где угодно

1800 INFORMATION 09.10.2008 07:37

да. Но я предполагаю, что вопрос был упрощен. Технически объявление и определение могут быть в одном исходном файле. Но это ограничивает использование класса другими классами.

Martin York 09.10.2008 07:40

Забыл сказать, что положил их в один файл. Это сработало как шарм. Спасибо!

Jason Baker 09.10.2008 07:43

на самом деле не только POD, это также должен быть тип int (int, short, bool, char ...)

Matt Curtis 09.10.2008 08:56

Обратите внимание, что это не просто вопрос того, как инициализируется значение: определенные таким образом целочисленные типы const могут быть преобразованы реализацией в константы времени компиляции. Это не всегда то, что вам нужно, поскольку это увеличивает бинарную зависимость: клиентский код требует перекомпиляции, если значение изменяется.

Steve Jessop 09.10.2008 15:19

Хотя, поскольку это личное, думаю, это не проблема. Извиняюсь.

Steve Jessop 09.10.2008 15:24

он должен быть целым и всегда быть постоянным выражением (никогда не функцией)

Ramadheer Singh 21.05.2010 02:48

@Martin: в дополнение к исправлению s / POD / интегрального типа /, если адрес когда-либо берется, также должно быть определение. Как это ни странно звучит, объявление с инициализатором в определении класса не является определением. шаблонная идиома const предоставляет обходной путь для случаев, когда вам нужно определение в файле заголовка. Другой и более простой обходной путь - это функция, которая производит значение локальной статической константы. Ура & hth.,

Cheers and hth. - Alf 15.10.2010 22:13

Может ли инициализация быть помещена в заголовок, если вы используете наблюдатели заголовков, вызывающие включение заголовка только один раз?

Sebastian Hoffmann 07.01.2012 18:44

@Paranaix: 1) Вы имеете в виду Header Guards. 2) Как заметил Мэтт Кертис: только специальный случай const int / char / bool (см. Последний абзац выше)

Martin York 08.01.2012 00:42

Вы можете добавить пояснение, что int foo :: i = 0; не должен находиться внутри функции (включая основную функцию). У меня это было в начале моей основной функции, и мне это не нравится.

qwerty9967 15.04.2013 23:33

@MattCurtis "или любого другого типа, если вы используете C++ 11". Хотя стоит добавить только я понимаю, если дополнительно указано как constexpr для нецелых типов.

wardw 27.04.2013 21:23

@wards, можно ли использовать constexpr с нецелочисленными типами?

sasha.sochka 31.07.2013 02:18

@ sasha.sochka Может вернуть любой en.cppreference.com/w/cpp/concept/LiteralType

Brian Gordon 11.02.2014 01:01

@ sasha.sochka не с типами, у которых есть деструкторы не по умолчанию.

Utkarsh Bhardwaj 03.06.2015 10:34

Я был неправильно процитирован в этом ответе, его содержание принадлежит его редакторам, а не мне. Я, конечно, не сказал «любой константный тип» или «любой тип в C++ 11».

Matt Curtis 18.07.2015 13:30

@LokiAstari Все в порядке - спасибо за редактирование. Я только что заметил в некоторых ответах на SO, иногда сбивает с толку, кто что говорит. (Также иногда мои ответы были отредактированы так, как я думаю, меняет смысл. Думаю, это цена геймификации!)

Matt Curtis 22.07.2015 14:50

На самом деле в файле CPP достаточно определение (вместо инициализации, которая не является обязательной): int foo::i;

danger89 17.09.2018 19:32

Вы упомянули, что это может быть в источнике, если не передано. Будет ли за / против делать это, если значение "Private Static", так как это не может быть разделено, и, по моему мнению, это упрощает запись, но я не знаю, отрицательно ли это делать это иначе, чем для будущего использования, если вы планируете использовать его, вы не можете просто сделать его общедоступным, тогда вам придется объявить его в заголовке.

Jeremy Trifilo 01.01.2019 01:37

Что, если это не элементарный тип данных?

Dustin K 19.02.2020 17:26

@DustinK Первый метод, показанный выше, работает для всех типов. Второй прием - это оптимизация для целочисленных типов.

Martin York 19.02.2020 22:19

Для Переменная:

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.

Martin York 09.10.2008 07:44

С тех пор, как C++ позволяет просто хорошо работать с объявлением в классе и без определения интегральных типов. С самого C++ 98 или C++ 03 или когда? Пожалуйста, поделитесь аутентичными ссылками. Стандартные формулировки C++ не синхронизированы с компиляторами. Они упоминают, что член должен быть определен, если они используются. Итак, мне не нужно цитирование стандарта C++, хотя

smRaj 20.09.2014 19:42

Интересно, почему переменные private могут быть инициализированы вне класса здесь, можно ли это сделать и для нестатических переменных.

Krishna Oza 10.07.2015 14:19

Вы нашли объяснение? @Krishna_Oza

nn0p 14.09.2016 12:16

@ nn0p еще нет, но инициализация нестатических частных переменных вне Class не имеет смысла в Cpp.

Krishna Oza 14.09.2016 13:20

Возникла проблема с инициализацией статической переменной const в определении класса. Если вы поместите это определение класса в файл .h или .hpp и включите его в несколько файлов .cpp, вы можете получить предупреждения о "неиспользуемой переменной" во время компиляции. Статические переменные лучше инициализировать в .cpp файле классов.

bearvarine 27.09.2017 16:14

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 просто предотвращает использование нескольких определений на единицу перевода.

Paul Fultz II 20.04.2012 19:43

по поводу хорошего стиля: вы должны добавить комментарий в конце, если: #endif // FOO_H

Riga 04.07.2012 17:08

Это работает, только если у вас есть только один модуль компиляции, который включает foo.h. Если два или более cpps включают foo.h, что является типичной ситуацией, каждый cpp объявляет одну и ту же статическую переменную, поэтому компоновщик будет жаловаться на несколько определений `foo :: i ', если вы не используете компиляцию пакета с файлами (compile только один файл, включающий все cpps). Но хотя компиляция пакетов - это замечательно, решение проблемы - объявить (int foo :: i = 0;) в cpp!

Alejadro Xalabarder 31.12.2013 06:51

Или просто используйте #pragma once

tambre 25.10.2016 16:36

Для будущих тех, кто рассматривает этот вопрос, я хочу указать, что вам следует избегать того, что monkey0506 предлагает.

Заголовочные файлы предназначены для объявлений.

Заголовочные файлы компилируются один раз для каждого файла .cpp, который прямо или косвенно #includes их, и код вне какой-либо функции запускается при инициализации программы перед main().

Если поместить: foo::i = VALUE; в заголовок, foo:i будет присвоено значение VALUE (что бы оно ни было) для каждого файла .cpp, и эти назначения будут происходить в неопределенном порядке (определяемом компоновщиком) перед запуском main().

Что, если #define VALUE будет другим номером в одном из наших файлов .cpp? Он будет компилироваться нормально, и мы не сможем узнать, какой из них выиграет, пока мы не запустим программу.

Никогда не помещайте исполняемый код в заголовок по той же причине, по которой вы никогда не помещаете #include в файл .cpp.

включить охранники (которые, я согласен, вы всегда должны использовать) защищают вас от чего-то другого: один и тот же заголовок косвенно используется #included несколько раз при компиляции одного файла .cpp

Вы, конечно, правы насчет этого, за исключением случая с шаблоном класса (о котором не спрашивают, но я имею дело с большим количеством). Поэтому, если класс полностью определен, а не шаблон класса, поместите эти статические члены в отдельный файл CPP, но для шаблонов классов определение должно быть в той же единице перевода (например, в файле заголовка).

monkey0506 19.01.2013 00:22

@ monkey_05_06: Кажется, это аргумент, позволяющий избежать статического члена в шаблонном коде: у вас уже есть один статический член для каждого экземпляра класса. проблема усугубляется возможной компиляцией заголовка в несколько файлов cpp ... Вы можете получить множество противоречащих друг другу определений.

Joshua Clayton 26.02.2013 09:13
publib.boulder.ibm.com/infocenter/macxhelp/v6v81/… This link depicts instantiating static template members int the main function, which is cleaner, if a bit of a burden.
Joshua Clayton 26.02.2013 09:29

Ваш аргумент действительно огромен. Во-первых, вы не можете #define VALUE, потому что имя макроса не является допустимым идентификатором. И даже если бы вы могли - кто бы это сделал? Заголовочные файлы предназначены для объявления -? Да ладно .. Единственные случаи, когда вам следует избегать размещения значений в заголовке, - это бороться с odr-used. А размещение значения в заголовке может привести к ненужной перекомпиляции всякий раз, когда вам нужно изменить значение.

Aleksander Fular 04.03.2016 18:39

Вы также можете включить назначение в файл заголовка, если используете защиту заголовка. Я использовал эту технику для созданной мной библиотеки 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

Ciro Santilli TRUMP BAN IS BAD 19.01.2018 11:56

Это служит вашей цели?

//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, который в настоящее время становится новым стандартом.

Grebu 21.07.2017 23:38

Проблема компоновщика, с которой вы столкнулись, вероятно, вызвана:

  • Предоставление определения как класса, так и статического члена в файле заголовка,
  • Включение этого заголовка в два или более исходных файла.

Это обычная проблема для тех, кто начинает с C++. Статический член класса должен быть инициализирован в одной единице перевода, то есть в одном исходном файле.

К сожалению, статический член класса должен быть инициализирован вне тела класса. Это усложняет написание кода только для заголовков, поэтому я использую совершенно другой подход. Вы можете предоставить свой статический объект с помощью статической или нестатической функции класса, например:

class Foo
{
    // int& getObjectInstance() const {
    static int& getObjectInstance() {
        static int object;
        return object;
    }

    void func() {
        int &object = getValueInstance();
        object += 5;
    }
};

Я все еще полный новичок в том, что касается C++, но мне это кажется блестящим, большое вам спасибо! Я получаю идеальное управление жизненным циклом одноэлементного объекта бесплатно.

Rafael Kitover 06.11.2019 18:18

Шаблон статического конструктора 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);
}

GitHub вверх по течению.

Скомпилируйте и запустите:

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-использование это.

Другие вопросы по теме