是否可以在 C++ 类中声明一个虚拟静态常量值?

Posted

技术标签:

【中文标题】是否可以在 C++ 类中声明一个虚拟静态常量值?【英文标题】:Is it possible to declare a virtual static constant value in a C++ class? 【发布时间】:2012-06-10 13:10:05 【问题描述】:

我想要一个具有常量字段的基类(例如与类关联的唯一 ID,在编译后无法修改)。到目前为止,static const 声明就可以了。现在,我想继承这个基类,并确保这个类的子类确实有相同的字段,但有自己的值。我该怎么做?

假设,我想要一个名为 Base 的基类,其 ID 字段的 int 值为 0。然后,我想要类 A,@ 987654327@ 和 C,它们都是 Base 的公共子代,我想确保这些子代也有 ID 字段,其各自的值为 1、2 和 3(通过当然',我的意思是如果它们没有明确声明的 ID,则会出现编译器错误)。

如果我能设法构建这个场景,我的期望是请求Base* 指针的ID 字段,我应该得到不同的值,具体取决于指针是否创建为new A()new B()new C()

我的猜测是将ID 声明为virtual static const,这当然没有意义并且会导致编译器错误。

但是我该怎么做才能达到所描述的结果呢? (我唯一能想到的是将ID 声明为一个返回整数的虚函数,然后将值硬编码到函数体中,但我正在寻找更优雅的东西。)

提前谢谢你!

【问题讨论】:

多态只对成员函数有效,对数据成员无效。 我已经写了一个基于模板的替代方案:***.com/a/36797199/1529139 【参考方案1】:

static方法不能是virtual,任何数据成员都不能是virtual

但您可以在派生类中隐藏static 字段并使用virtual 方法返回它们。

class A

public:
    static const int ID = 0;
    virtual int getID()  return A::ID; 
;
class B : A

public:
    static const int ID = 1;
    virtual int getID()  return B::ID; 
;

替代方案:

class A

public:
    A(int id = 0) : ID(id) 
    const int ID;
    getID()  return ID; 
;
class B : public A

public:
    B() : A(1) 
;

【讨论】:

是的,这是我目前知道的唯一解决方案。但是有没有办法以更优雅/简单的方式解决这个问题?或者,如果这是唯一的方法,那么我的问题是:为什么适用于函数的虚拟继承不适用于数据成员?我理解为什么实际上static virtual 没有意义,但我真的不明白为什么一个简单的virtual const int 字段没有意义...... @SiskaÁdám 因为没有为数据成员定义多态行为。这就是语言的设计方式。这是 IMO 的一件好事。 @SiskaÁdám 我发布了一个替代方案,但我仍然觉得第一个更好。 太疯狂了,在 C++11 中你甚至可以创建虚函数constexpr。这并不是非常有用,因为当您实际使用虚拟继承时它将无法编译。虽然f<A().getID()>(); 实际编译,但鉴于f 是一个采用整数的模板函数... @SiskaÁdám 通常成员表示状态。如果使用继承,则假定类 B 是类 A,并且具有其所有成员以及其他成员,但它具有 A 的成员。不覆盖。它们可以不同,但​​不能被覆盖。除了您发布的示例之外,我找不到其他有用的示例,这可能不是一个好的设计 - 还有其他机制可用于确定对象的运行时类型(请参阅 RTTI)。这个ID 的东西似乎是个黑客。也许只有我一个人,因为我不习惯数据成员多态的概念,但我真的看不到任何实用性。【参考方案2】:

在 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::nameShape::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 函数将始终返回对完全相同对象的引用。在只读的虚拟静态成员上,这不应该有区别(除非构造函数实际上以不同的方式初始化每个实例),但在读写的虚拟静态成员上,更新它们会有所不同。

【讨论】:

以上是关于是否可以在 C++ 类中声明一个虚拟静态常量值?的主要内容,如果未能解决你的问题,请参考以下文章

C++父类中声明了一个虚函数以后 是否在子类 以及子类的子类中 都要声明并重写这个函数?

Salesforce基础 - Apex常量

常量数据的初始化值

C# 中的虚拟/抽象字段

关于“只有静态常量整型数据成员才可以在类中初始化”

如何在 cpp 类中有静态常量?