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



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



class foo
{
private:
static int i;
};

int foo::i = 0;


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

710   17  

17 ответов:

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

на переменная:

фу.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 файлов? Он будет компилироваться нормально, и у нас не будет способа узнать, какой из них выиграет, пока мы не запустим программу.

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

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

С компилятором 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

    Ничего не найдено.