C++ 静态成员变量:深入理解与应用

背景回顾

在《全局变量简介》中,我们介绍了全局变量;在《静态局部变量》中,又介绍了静态局部变量。这两类变量均具有静态存储期:它们在程序启动时创建,在程序结束时销毁,即使离开作用域也保留其值。

示例:

#include <iostream>

int generateID()
{
    static int s_id{ 0 }; // 静态局部变量
    return ++s_id;
}

int main()
{
    std::cout << generateID() << '\n'; // 1
    std::cout << generateID() << '\n'; // 2
    std::cout << generateID() << '\n'; // 3
    return 0;
}

类中的两种新 static 用法

类类型为 static 关键字带来了两种新用途:

  1. 静态成员变量(本节讨论);
  2. 静态成员函数(下节讨论)。

静态成员变量

与普通成员变量的区别

先观察一个普通类:

#include <iostream>

struct Something
{
    int value{ 1 };
};

int main()
{
    Something first{};
    Something second{};

    first.value = 2;

    std::cout << first.value << '\n';  // 2
    std::cout << second.value << '\n'; // 1
}

每个对象各自拥有普通成员变量的副本。
若将成员变量声明为 static,则该变量被所有对象共享

#include <iostream>

struct Something
{
    static int s_value; // 声明为静态
};

int Something::s_value{ 1 }; // 定义并初始化

int main()
{
    Something first{};
    Something second{};

    first.s_value = 2;

    std::cout << first.s_value << '\n';  // 2
    std::cout << second.s_value << '\n'; // 2
}

静态成员不属于任何对象

尽管可以通过对象访问静态成员(obj.s_value),但即使未创建任何对象,静态成员也依然存在。它们的生命周期与程序相同,本质上是位于类作用域内的全局变量

关键洞见
静态成员是位于类作用域中的全局变量

因此,推荐使用类名加作用域解析运算符访问:

Something::s_value = 2;

最佳实践
使用 类名::静态成员 的形式访问静态成员。


定义与初始化静态成员变量

在类内声明静态成员变量时,仅向编译器告知其存在(类似前向声明)。
必须在类外全局作用域显式定义(并可初始化):

int Something::s_value{ 1 }; // 定义并赋初值
  • 若未显式初始化,静态成员将零初始化
  • 该定义不受访问控制限制;即使变量在类中为 private,也可在类外定义。
  • 非模板类
    • 若类定义在头文件,静态成员定义通常放在对应 .cpp 文件;
    • 也可声明为 inline 并放在头文件中,以支持仅头文件库。
  • 模板类
    • 模板静态成员定义通常紧跟类模板定义放在头文件中(隐式 inline,不违 ODR)。
  • 切勿把非 inline 静态成员定义放在头文件,否则多重包含会导致链接错误。

类内初始化静态成员

若静态成员为:

  • const 整型(含 charbool);
  • const 枚举类型;

则可在类内直接初始化

class Whatever
{
public:
    static const int s_value{ 4 }; // 无需额外定义行
};

此外,C++17 起可声明 inline 静态成员:

class Whatever
{
public:
    static inline int s_value{ 4 }; // 类内定义并初始化
};

constexpr 静态成员自 C++17 起也隐式 inline,可直接初始化:

#include <string_view>

class Whatever
{
public:
    static constexpr double s_value{ 2.2 };
    static constexpr std::string_view s_view{ "Hello" };
};

最佳实践
将静态成员声明为 inlineconstexpr,以便在类内完成定义与初始化。


静态成员变量示例:生成唯一 ID

#include <iostream>

class Something
{
private:
    static inline int s_idGenerator{ 1 }; // 共享计数器
    int m_id{};

public:
    Something() : m_id{ s_idGenerator++ } {}

    int getID() const { return m_id; }
};

int main()
{
    Something first, second, third;

    std::cout << first.getID() << '\n';  // 1
    std::cout << second.getID() << '\n'; // 2
    std::cout << third.getID() << '\n';  // 3
}

每个新对象获得递增的唯一 ID,便于调试区分。


静态成员的其他用途

  • 共享查找表:如预计算数组。设为 static 后,所有对象共用一份,节省内存。
  • 类型推导
    • 静态成员可使用 auto 或 CTAD 进行类型推导;
    • 非静态成员不允许,以避免潜在歧义。

示例:

#include <utility> // std::pair

class Foo
{
private:
    // 非静态成员禁止 auto / CTAD
    // auto m_x{ 5 };          
    // std::pair m_v{ 1, 2.3 }; 

    // 静态成员允许
    static inline auto s_x{ 5 }; 
    static inline std::pair s_v{ 1, 2.3 }; 
};

小结

  • 静态成员变量被所有对象共享,生命周期贯穿整个程序。
  • 推荐通过 类名::成员 访问。
  • 必须在类外(或 inline/constexpr 内)显式定义,避免多重定义。
  • 可用于唯一 ID、全局计数器、共享查找表等场景,并可利用 auto / CTAD 进行类型推导。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"