Статические члены класса в C++: короткое руководство
Как объявить и правильно инициализировать static-поля и методы, где их определять и что изменилось с C++17. Разбор на рабочих примерах с проверкой каждого шага.

Вы объявили static int count; внутри класса, собрали проект и получили undefined reference to `MyClass::count' на этапе линковки. Компилятор был доволен, линкер — нет. Это самая частая ошибка со статическими членами, и она возникает потому, что объявление в теле класса не создаёт саму переменную. В этом руководстве разберём, как объявлять и определять статические поля и методы, где именно писать определение и что упростил C++17. На проработку уйдёт минут двадцать. Примеры собирались GCC 13 и Clang 17 в режиме -std=c++17.
Что нужно знать до начала
Понадобится компилятор с поддержкой C++17: GCC 7+, Clang 5+ или MSVC 2017+. Флаг стандарта — -std=c++17 для GCC и Clang. Предполагается, что вы уже писали обычные классы: понимаете, что такое поле и метод, разделение на заголовок .h и файл реализации .cpp.
Статический член принадлежит классу целиком, а не конкретному объекту. Одна переменная на все экземпляры; статический метод вызывается без объекта вообще.
Шаг 1. Статическое поле: объявление и определение
В теле класса вы только объявляете статическое поле. Само хранилище нужно определить отдельно — обычно в .cpp-файле.
// counter.h
#pragma once
class Counter {
public:
Counter() { ++instances; }
~Counter() { --instances; }
static int instances; // объявление, памяти ещё нет
};
// counter.cpp
#include "counter.h"
int Counter::instances = 0; // определение: здесь появляется переменная
Определение пишется без ключевого слова static и вне класса, с полным именем Counter::instances. Если убрать эту строку из .cpp, вернётся тот самый undefined reference.
Проверка: соберите файл, который создаёт пару объектов и печатает Counter::instances. После двух конструкторов в области видимости должно вывестись 2.
Почему определение не кладут в заголовок
Если заголовок подключить в двух .cpp-файлах, определение попадёт в оба объектных файла, и линкер выдаст multiple definition of `Counter::instances'. Определение должно быть ровно одно на всю программу — это правило одного определения (ODR)1.
Шаг 2. Статические константы
Целочисленную статическую константу можно инициализировать прямо в классе:
class Buffer {
public:
static const int maxSize = 1024; // ок для целочисленных const
};
Такое значение работает как константа времени компиляции: его можно подставлять в размеры массивов и switch. Но как только вы возьмёте на неё ссылку или адрес (например, передадите в функцию, принимающую const int&), для этого всё равно понадобится определение вне класса. Для float, double и составных типов инициализация прямо в классе до C++17 была запрещена.
Шаг 3. inline-переменные из C++17 — самый простой способ
С C++17 у статических полей появился ключ inline. Он разрешает определить и проинициализировать поле прямо в заголовке — линкер сам схлопнет дубликаты в один объект.
// config.h
#pragma once
#include <string>
class Config {
public:
inline static int version = 3; // определение прямо здесь
inline static std::string name = "prod"; // и для нестандартных типов
};
Отдельный .cpp с определением больше не нужен. Это работает и для static constexpr — такие члены неявно inline начиная с C++17, отдельное определение им не требуется.
Правило простое: на C++17 и выше объявляйте статические поля черезinline staticв заголовке — и забудьте про парную строку в.cpp. Старый способ оставьте для кода, который обязан собираться на C++11 и C++14.
Шаг 4. Статические методы
Статический метод не получает this и не может обращаться к нестатическим полям. Он видит только другие статические члены.
// registry.h
#pragma once
class Registry {
public:
static int count() { return total; } // объявлен и определён в классе
static void add() { ++total; }
private:
inline static int total = 0;
};
Вызывается через имя класса, без объекта: Registry::add(); и int n = Registry::count();. Внутри статического метода обращение к обычному полю не скомпилируется — компилятор скажет invalid use of member ... in static member function.
Проверка: вызовите Registry::add() дважды и напечатайте Registry::count() — должно быть 2, при этом ни одного объекта Registry вы не создавали.
Способы инициализации: что выбрать
| Способ | Где писать | Для чего подходит | Стандарт |
|---|---|---|---|
| Объявление в классе + определение в .cpp | заголовок + .cpp | любые типы, старые компиляторы | C++11+ |
static const int = ... в классе | заголовок | целочисленные константы | C++11+ |
inline static x = ... | только заголовок | любые типы, header-only библиотеки | C++17+ |
static constexpr x = ... | только заголовок | константы времени компиляции | C++17+ (неявно inline) |
Если не получилось
undefined reference to `Class::field'— вы объявили поле в классе, но не определили его в.cpp. Добавьте строкуint Class::field = 0;или перейдите наinline static.multiple definition of `Class::field'— определение попало в заголовок безinline, и заголовок подключён в нескольких единицах трансляции. Либо перенесите определение в один.cpp, либо добавьтеinline.invalid use of member 'x' in static member function— из статического метода вы обращаетесь к нестатическому полю. У статического метода нетthis; передайте объект параметром или сделайте поле статическим.ISO C++ forbids in-class initialization of non-const static member— вы инициализируете нецелочисленное поле прямо в классе на старом стандарте. Соберите с-std=c++17и добавьтеinline, либо вынесите инициализацию в.cpp.- Значение поля «сбрасывается» между вызовами — вероятно, поле на самом деле нестатическое и живёт в каждом объекте отдельно. Проверьте, что перед типом стоит
static.
1 ODR (One Definition Rule) — правило одного определения: у каждой переменной и функции с внешним связыванием во всей программе должно быть ровно одно определение. Нарушение приводит к ошибкам линковки или неопределённому поведению.
Что дальше
Разобравшись с базой, посмотрите на порядок инициализации статических объектов между разными единицами трансляции — это отдельная тема с известной ловушкой «static initialization order fiasco», когда один статический объект зависит от другого, а порядок их создания не определён. Для конфигурации и счётчиков в header-only коде по умолчанию берите inline static: меньше строк, меньше поводов для ошибок линковки. Если поддерживаете сборку на C++11, оставайтесь на связке «объявление в классе + определение в .cpp».
Задачи: математика, физика, химия: Разбор задачи по шагам с проверкой и похожей задачей
Спросить за 10 ₽Источники: cppreference — static members, cppreference — inline specifier
Частые вопросы
А если у меня компилятор без C++17, только C++11?
Почему определение статического поля пишут без слова static?
Можно ли положить static constexpr прямо в заголовок?
Чем статический метод отличается от обычного на практике?
Не сработало: собирается, но при линковке всё равно multiple definition. Что смотреть?
Статические поля потокобезопасны?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.