c++“非整型、非常量”的静态成员的类外定义
preface
template<class TYPE>
class Singleton {
public:
//global access point
static TYPE *instance() {
return &Instance;
};
public:
//constructor
Singleton() {
}
//destructor
virtual ~Singleton() {
}
private:
//instance for TYPE
static TYPE Instance;
};
typedef Singleton<Mgr> MGR;
#define MGR() MGR::instance()
template <>
Mgr Singleton<Mgr>::Instance = Mgr();
一、静态成员类内声明、类外定义
template <>
Mgr Singleton<Mgr>::Instance = Mgr();
不是普通函数调用语句,而是在定义一个静态存储期对象:
Singleton<Mgr>::Instance
它的类型是:Mgr,右边的 Mgr() 表示用Mgr 的默认构造函数初始化这个对象。
所以执行时机大致是:
1. 程序启动。
2. 运行 C/C++ 运行时初始化逻辑。
3. 初始化全局对象和静态对象。
4. 构造 Singleton<Mgr>::Instance。
5. 进入 main()。
6. 后续代码调用 MGR() 时,只是返回这个已经存在的对象地址。
也就是说,MGR() 本身不会创建对象,它只是取地址:
static TYPE *instance() {
return &m_Instance;
}
真正创建对象的是这句全局定义:
Mgr Singleton<Mgr>::Instance = Mgr();
补充一句:严格讲,Mgr() 的构造会在main() 前发生;但 Mgr::init() 不会自动执行,它是在 main() 里这里手动调用的:MGR()->init()
这是静态成员变量的模板显式特化定义,不是函数调用:“
- m_slInstance 具有静态存储期,在 main() 之前构造
- instance() 只是返回已存在对象的地址,不创建新对象
- init() 需要手动调用”
二、 类外定义
template <class TYPE>
class Singleton {
static TYPE Instance; // 类内:仅声明
};
// 类外:必须定义
template <>
Mgr Singleton<Mgr>::Instance = Mgr();
类内只是声明,告诉编译器"有这么个变量",但不分配内存。类外定义才真正分配存储空间。
如果只有声明没有类外定义,链接时会报错:
undefined reference to `Singleton<Mgr>::Instance`
main() 之前初始化
静态成员变量具有静态存储期,初始化分两阶段:
程序启动
└─ 1. 静态初始化(zero-initialization) ← 编译期完成
└─ 2. 动态初始化(调用构造函数) ← m_slInstance 在这里构造
└─ main()
这是 C++ 标准保证的,不需要手动触发。
补充一点
有一个例外情况:static const int 这类整型常量可以在类内直接初始化,不需要类外定义(C++11 起 constexpr 也可以):
class Foo {
static const int N = 42; // 类内直接初始化,合法
static constexpr int M = 10; // C++11 起合法
};
但对于非整型、非常量的静态成员(比如 Mgr类型),就必须类外定义。
三、 补充的细节:
初始化阶段更精确地说分两步:先是静态初始化(zero-initialization),再是动态初始化(调用构造函数)。你描述的构造发生在动态初始化阶段。= Mgr() 是 copy-initialization 语法。
C++17 起有强制 copy elision,效果等同于直接默认构造。
C++17 之前编译器通常也会优化掉拷贝/移动,但理论上要求拷贝/移动构造函数可访问。
跨翻译单元的顺序未定义(static initialization order fiasco)。
如果 Mgr 的构造函数依赖其他翻译单元的全局对象,可能出问题。这也是为什么把真正的初始化逻辑放在 init() 里、在 main() 中手动调用——此时所有全局对象已构造完毕,依赖关系更可控。
、
更多推荐
所有评论(0)