Как инициализировать частные статические члены в C ++? - PullRequest
460 голосов
/ 09 октября 2008

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

class foo
{
    private:
        static int i;
};

int foo::i = 0;

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

Ответы [ 17 ]

498 голосов
/ 09 октября 2008

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

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

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;
};
26 голосов
/ 27 декабря 2012

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

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

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

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

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

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

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

21 голосов
/ 12 июля 2017

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

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

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

struct X
{
    inline static int n = 1;
};
19 голосов
/ 09 октября 2008

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

class A
{
    static B b;
}

__declspec(selectany) A::b;

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

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

16 голосов
/ 09 октября 2008
int foo::i = 0; 

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

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

11 голосов
/ 23 ноября 2014

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

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

11 голосов
/ 07 января 2012

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

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

class foo
{
private:
    static bar i;
};

bar foo::i = VALUE;
#endif

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

5 голосов
/ 12 апреля 2013

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

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

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

Приведенный выше код обладает «бонусом»: не требуется CPP / исходный файл. Опять же, метод, который я использую для своих библиотек C ++.

4 голосов

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

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

GitHub upstream .

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...