静态类成员

一个类也允许有 static 成员,可以是变量或函数。这些被认为属于类范围,但不被视为普通会员; 它们具有静态存储持续时间(它们从程序的开始到结束),不依赖于类的特定实例,并且整个类只存在一个副本。

class Example {
    static int num_instances;      // Static data member (static member variable).
    int i;                         // Non-static member variable.

  public:
    static std::string static_str; // Static data member (static member variable).
    static int static_func();      // Static member function.

    // Non-static member functions can modify static member variables.
    Example() { ++num_instances; }
    void set_str(const std::string& str);
};

int         Example::num_instances;
std::string Example::static_str = "Hello.";

// ...

Example one, two, three;
// Each Example has its own "i", such that:
//  (&one.i != &two.i)
//  (&one.i != &three.i)
//  (&two.i != &three.i).
// All three Examples share "num_instances", such that:
//  (&one.num_instances == &two.num_instances)
//  (&one.num_instances == &three.num_instances)
//  (&two.num_instances == &three.num_instances)

静态成员变量不被认为是在类中定义的,只是声明的,因此它们的定义在类定义之外; 允许程序员(但不是必需的)在其定义中初始化静态变量。定义成员变量时,省略关键字 static

class Example {
    static int num_instances;               // Declaration.

  public:
    static std::string static_str;          // Declaration.

    // ...
};

int         Example::num_instances;         // Definition.  Zero-initialised.
std::string Example::static_str = "Hello."; // Definition.

因此,静态变量可以是不完整的类型(除了 void),只要它们稍后被定义为完整类型。

struct ForwardDeclared;

class ExIncomplete {
    static ForwardDeclared fd;
    static ExIncomplete    i_contain_myself;
    static int             an_array[];
};

struct ForwardDeclared {};

ForwardDeclared ExIncomplete::fd;
ExIncomplete    ExIncomplete::i_contain_myself;
int             ExIncomplete::an_array[5];

静态成员函数可以在类定义的内部或外部定义,与普通成员函数一样。与静态成员变量一样,在类定义之外定义静态成员函数时,省略关键字 static

// For Example above, either...
class Example {
    // ...

  public:
    static int static_func() { return num_instances; }

    // ...

    void set_str(const std::string& str) { static_str = str; }
};

// Or...

class Example { /* ... */ };

int  Example::static_func() { return num_instances; }
void Example::set_str(const std::string& str) { static_str = str; }

如果静态成员变量声明为 const 但不是 volatile,并且是一个整数或枚举类型,则可以在类定义中的声明处初始化它。

enum E { VAL = 5 };

struct ExConst {
    const static int ci = 5;              // Good.
    static const E ce = VAL;              // Good.
    const static double cd = 5;           // Error.
    static const volatile int cvi = 5;    // Error.

    const static double good_cd;
    static const volatile int good_cvi;
};

const double ExConst::good_cd = 5;        // Good.
const volatile int ExConst::good_cvi = 5; // Good.

Version >= C++ 11

从 C++ 11 开始,LiteralType 类型的静态成员变量(根据 constexpr 规则可以在编译时构造的类型)也可以声明为 constexpr; 如果是这样,它们必须在类定义中初始化。

struct ExConstexpr {
    constexpr static int ci = 5;                      // Good.
    static constexpr double cd = 5;                   // Good.
    constexpr static int carr[] = { 1, 1, 2 };        // Good.
    static constexpr ConstexprConstructibleClass c{}; // Good.
    constexpr static int bad_ci;                      // Error.
};

constexpr int ExConstexpr::bad_ci = 5;                // Still an error.

如果 constconstexpr 静态成员变量使用了 odr (非正式地,如果它的地址已被采用或被分配给引用),则它必须在类定义之外具有单独的定义。此定义不允许包含初始化程序。

struct ExODR {
    static const int odr_used = 5;
};

// const int ExODR::odr_used;

const int* odr_user = & ExODR::odr_used; // Error; uncomment above line to resolve.

由于静态成员不依赖于给定实例,因此可以使用范围运算符::访问它们。

std::string str = Example::static_str;

也可以像访问普通的非静态成员一样访问它们。这具有历史意义,但是比范围运算符更少使用,以防止对成员是静态还是非静态的混淆。

Example ex;
std::string rts = ex.static_str;

与非静态类成员一样,类成员可以访问静态成员而无需限定其作用域。

class ExTwo {
    static int num_instances;
    int my_num;

  public:
    ExTwo() : my_num(num_instances++) {}

    static int get_total_instances() { return num_instances; }
    int get_instance_number() const { return my_num; }
};

int ExTwo::num_instances;

它们不可能是天籁 13,也不是必须的; 因为它们不依赖于任何给定的实例,实例是否为 const 不会影响静态成员。

struct ExDontNeedMutable {
    int immuta;
    mutable int muta;

    static int i;

    ExDontNeedMutable() : immuta(-5), muta(-5) {}
};
int ExDontNeedMutable::i;

// ...

const ExDontNeedMutable dnm;
dnm.immuta = 5; // Error: Can't modify read-only object.
dnm.muta = 5;   // Good.  Mutable fields of const objects can be written.
dnm.i = 5;      // Good.  Static members can be written regardless of an instance's const-ness.

静态成员尊重访问修饰符,就像非静态成员一样。

class ExAccess {
    static int prv_int;

  protected:
    static int pro_int;

  public:
    static int pub_int;
};

int ExAccess::prv_int;
int ExAccess::pro_int;
int ExAccess::pub_int;

// ...

int x1 = ExAccess::prv_int; // Error: int ExAccess::prv_int is private.
int x2 = ExAccess::pro_int; // Error: int ExAccess::pro_int is protected.
int x3 = ExAccess::pub_int; // Good.

因为它们不依赖于给定的实例,所以静态成员函数没有 this 指针; 因此,除非传递实例,否则它们无法访问非静态成员变量。

class ExInstanceRequired {
    int i;

  public:
    ExInstanceRequired() : i(0) {}

    static void bad_mutate() { ++i *= 5; }                         // Error.
    static void good_mutate(ExInstanceRequired& e) { ++e.i *= 5; } // Good.
};

由于没有 this 指针,它们的地址不能存储在指向成员函数的指针中,而是存储在普通的指向函数的指针中。

struct ExPointer {
           void nsfunc() {}
    static void  sfunc() {}
};

typedef void (ExPointer::* mem_f_ptr)();
typedef void (*f_ptr)();

mem_f_ptr p_sf = &ExPointer::sfunc; // Error.
    f_ptr p_sf = &ExPointer::sfunc; // Good.

由于没有 this 指针,它们也不能是 constvolatile,也不能有 ref-qualifiers。他们也不可能是虚拟的。

struct ExCVQualifiersAndVirtual {
    static void   func()                {} // Good.
    static void  cfunc() const          {} // Error.
    static void  vfunc() volatile       {} // Error.
    static void cvfunc() const volatile {} // Error.
    static void  rfunc() &              {} // Error.
    static void rvfunc() &&             {} // Error.

    virtual static void vsfunc()        {} // Error.
    static virtual void svfunc()        {} // Error.
};

由于它们不依赖于给定的实例,因此静态成员变量被有效地视为特殊的全局变量; 它们是在程序启动时创建的,并在退出时被销毁,无论该类的实例是否存在。只存在每个静态成员变量的一个副本(除非该变量被声明为 thread_local(C++ 11 或更高版本),在这种情况下,每个线程有一个副本)。

静态成员变量与类具有相同的链接,无论该类具有外部链接还是内部链接。本地类和未命名的类不允许具有静态成员。