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

StackOverflow https://stackoverflow.com/questions/185844

Вопрос

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

class foo
{
    private:
        static int i;
};

int foo::i = 0;

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

Это было полезно?

Решение

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

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

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

фу.ч:

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

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

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

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

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

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

class A
{
    static B b;
}

__declspec(selectany) A::b;

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

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

int foo::i = 0; 

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

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

У меня недостаточно представителя здесь, чтобы добавить это в качестве комментария, но ИМО - это хороший стиль для написания ваших заголовков с помощью # include guards в любом случае, что, как заметил 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 () , он будет создан только один раз, что означает, что конструктор вызывается один раз. Это инициализирует переменную _list для получения необходимого вам значения. Любой последующий вызов getList просто вернет уже инициализированный объект _list .

Конечно, вы всегда должны обращаться к объекту _list , вызывая метод getList () .

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

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

Одна идиома была предложена по адресу: 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);
}

> hore pub.> H

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

Протестировано с помощью g ++ -std = c ++ 11 -Wall -Wextra , GCC 7.3, Ubuntu 18.04.

Также работает в файле 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 будет инициализирована.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Это отвечает вашим целям?

//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;
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top