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

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

class foo
{
    private:
        static int i;
};

int foo::i = 0;

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

Ответ 1

Объявление класса должно быть в файле заголовка (или в исходном файле, если не используется совместно).
Файл: foo.h

class foo
{
    private:
        static int i;
};

Но инициализация должна быть в исходном файле.
Файл: foo.cpp

int foo::i = 0;

Если инициализация находится в файле заголовка, то каждый файл, содержащий заголовочный файл, будет иметь определение статического члена. Таким образом, во время фазы ссылки вы получите ошибки компоновщика, поскольку код для инициализации переменной будет определен в нескольких исходных файлах.

Примечание: Мэтт Кертис: указывает, что С++ позволяет упростить приведенное выше, если статическая переменная-член имеет тип const int (например, int, bool, char). Затем вы можете объявить и инициализировать переменную-член непосредственно внутри объявления класса в файле заголовка:

class foo
{
    private:
        static int const i = 42;
};

Ответ 2

Для переменной :

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;
};

Ответ 3

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

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

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

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

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

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

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

Ответ 4

Поскольку С++ 17, статические члены могут быть определены в заголовке с ключевым словом inline.

http://en.cppreference.com/w/cpp/language/static

"Статический член данных может быть объявлен встроенным. Встроенный статический элемент данных может быть определен в определении класса и может указывать инициализатор элемента по умолчанию. Для него не требуется определение вне класса:

struct X
{
    inline static int n = 1;
};

Ответ 5

С компилятором Microsoft [1] статические переменные, которые не являются int -like, также могут быть определены в файле заголовка, но вне объявления класса, используя специальный __declspec(selectany) Microsoft.

class A
{
    static B b;
}

__declspec(selectany) A::b;

Заметьте, что я не говорю, что это хорошо, я просто говорю, что это можно сделать.

[1] В наши дни больше компиляторов, чем поддержка MSC __declspec(selectany) - по крайней мере, gcc и clang. Может быть, даже больше.

Ответ 6

int foo::i = 0; 

Является правильным синтаксисом для инициализации переменной, но она должна идти в исходном файле (.cpp), а не в заголовке.

Поскольку это статическая переменная, компилятор должен создать только одну копию. У вас должна быть строка "int foo: i", где в вашем коде, чтобы сообщить компилятору, куда его поместить, иначе вы получите сообщение об ошибке. Если это в заголовке, вы получите копию в каждом файле, который содержит заголовок, поэтому получите многократно определенные ошибки символов из компоновщика.

Ответ 7

Мне не хватает репутации здесь, чтобы добавить это как комментарий, но IMO это хороший стиль, чтобы написать ваши заголовки с # включить охранников в любом случае, что, как отмечалось Paranaix несколько часов назад, предотвратило бы ошибку множественного определения. Если вы уже не используете отдельный файл CPP, нет необходимости использовать его для инициализации статических нецелых членов.

#ifndef FOO_H
#define FOO_H
#include "bar.h"

class foo
{
private:
    static bar i;
};

bar foo::i = VALUE;
#endif

Я не вижу необходимости использовать для этого отдельный файл CPP. Конечно, вы можете, но нет технической причины, по которой вам следует.

Ответ 8

Если вы хотите инициализировать некоторый составной тип (строка f.e.), вы можете сделать что-то вроде этого:

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().

Ответ 9

Вы также можете включить назначение в файл заголовка, если используете средства защиты заголовков. Я использовал этот метод для библиотеки С++, которую я создал. Другим способом достижения такого же результата является использование статических методов. Например...

class Foo
   {
   public:
     int GetMyStatic() const
     {
       return *MyStatic();
     }

   private:
     static int* MyStatic()
     {
       static int mStatic = 0;
       return &mStatic;
     }
   }

В приведенном выше коде есть "бонус", не требующий файла CPP/source. Опять же, метод, который я использую для своих библиотек на С++.

Ответ 10

Шаблон статического конструктора, который работает для нескольких объектов

Одна идиома была предложена на: fooobar.com/questions/17690/..., но здесь идет более чистая версия, которая не требует создания нового метода для каждого члена.

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 upstream.

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

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

Смотрите также: статические конструкторы в C++? Мне нужно инициализировать частные статические объекты

Проверено на Ubuntu 19.04.

Ответ 11

Я следую идее Карла. Мне это нравится, и теперь я использую его также. Я немного изменил нотацию и добавил некоторые функции

#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

Ответ 12

Также работает в файле 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

Ответ 13

Как насчет метода 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 была бы инициализирована.

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

Ответ 14

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

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

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

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

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

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

Ответ 15

Один способ "старой школы" определить константы - заменить их enum:

class foo
{
    private:
        enum {i = 0}; // default type = int
        enum: int64_t {HUGE = 1000000000000}; // may specify another type
};

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

Ответ 16

Я просто хотел упомянуть кое-что немного странное для меня, когда впервые встретил это.

Мне нужно было инициализировать частный статический член данных в классе шаблона.

в .h или .hpp, он выглядит примерно так, чтобы инициализировать статический член данных класса шаблона:

template<typename T>
Type ClassName<T>::dataMemberName = initialValue;

Ответ 17

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

//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;
}