在 C++ 中拥有虚拟静态成员确实非常有用。它们可以很容易地添加到语言中,不需要新的关键字。以下代码示例为图形库命名形状类型:
class Shape {
public:
static constinit virtual std::string name = delete;
static constexpr virtual bool closed = delete;
...
};
class Circle : public Shape {
public:
static constinit std::string name override { "circle" };
static constexpr bool close override { true };
...
};
class Line : public Shape {
public:
static constinit std::string name override { "line" };
static constexpr bool close override { false };
...
};
这声明Shape
为抽象基类,Shape::name
并Shape::closed
通过= delete
.
虚拟静态成员的空间可以分配在同一个 VTable 中,该 VTable 已经用于虚拟函数调用。如果所有虚拟静态成员都是constinit
(C++20 中的新成员)或constexpr
,则 VTable 可以写入只读内存,目前大多数编译器也将其写入其中。如果不是,则必须将 VTable 放置在读写内存中。
一般来说,虚拟静态成员不需要是const
,它们也可以是读写的。
虚拟静态成员既可以使用类名作为前缀访问(它们的行为与普通静态成员一样),也可以通过对象访问,其中对象的 VTable 指针将用于访问正确的 VTable。
只要它们不在标准中,就可以使用虚函数来模拟它们,它返回对局部静态变量的引用:
virtual const std::string& get_name() const {
static const std::string name { "circle" };
return name;
}
如果派生类不覆盖静态成员(分别是虚拟 getter 函数),则真正的虚拟静态成员和模拟的虚拟静态成员之间的语义有点不同:父类和子类之间的真正虚拟静态成员实际上会引用该对象的不同实例,并为父级和每个子级调用构造函数,它不会覆盖虚拟静态成员。但是模拟的 getter 函数总是会返回对完全相同的对象的引用。在只读虚拟静态成员上,这不应该产生影响(除非构造函数实际上以不同方式初始化每个实例),但在读写虚拟静态成员上,更新它们会产生影响。