为什么没有添加虚拟静态成员作为C++的一个特性?

Why have virtual static members not been added as a feature of C++?

我刚读(第k次)

C++静态虚拟成员?

这是一个关于模拟虚拟静态成员的问题。我的问题是,什么使C++标准委员会(或之前的Bjarne Stroustrup)不将这个特性添加到C?他们知道会破坏什么吗?或者妨碍任何东西的性能(即使不使用)?

为了更好地说明我将要接管的功能定义本身,下面是一些代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// This does not compile!
class Base {
    // A pure virtual member - perhaps need to indicate that somehow
    virtual static const string ToWhom;
    void sayHello() {
        cout <<"Hello," << ToWhom <<"!" << endl;
    }
};
class World : public Base {
    static virtual const string ToWhom ="the entire world"s; // C++14 literal
};
class Everybody : public Base {
    static virtual const string ToWhom ="everybody around"s;
};

注:我不是在问你的意见,也不是在问增加这些是不是一个好主意,我是在问历史和官方的考虑。


首先,让我们看一个静态虚拟的无效示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// WARNING: This does not compile !!!
class Base {
    static virtual string toWhom() {
        return"unknown";
    }
    static void sayHello() {
        cout <<"Hello," << toWhom() <<"!" << endl;
    }
};
class World : public Base {
    static virtual string toWhom() {
        return"world";
    }
};
class Everybody : public Base {
    static virtual string toWhom() {
        return"everybody";
    }
};

这样可以做到:

1
2
3
// WARNING: This does not work !!!
Everybody::sayHello(); // Prints"Hello, everybody!"
World::sayHello(); // Prints"Hello, world!"

然而,问题是这样的调度在不改变C++中静态函数调用方式的情况下是不容易实现的。

回想一下,非静态成员函数隐式地获取this参数。它是this参数,携带有关虚拟函数的信息。但是,当调用静态函数时,不会传递任何标识当前类的内容(即上面示例中的HelloEverybody)。没有隐式参数,因此无法访问虚拟函数。

回到上面的例子,考虑当Base::sayHello调用toWhom()时会发生什么。它需要有一定的上下文来决定应该调用哪一个函数——即Base::toWhomWorld::toWhomEverybody::toWhom。不仅缺少这些信息,而且在语言中也没有现有的机制,我们可以通过类似于将指向虚拟函数的指针添加到类的数据中的方式"依附"此功能。

虽然这种调用机制确实可以更改,但是语言的作者没有看到这样做的令人信服的原因。


一个虚拟方法需要一个虚拟表,一个虚拟表需要一个带有vtable指针的实例,静态成员方法不能通过实例调用,因此这是不可能的。

从您问题中描述的"问题"来看,您可能期望使用这种格式的多态行为:

1
2
Everybody::sayHello();
World::sayHello();

但这并不要求多态性,因为你指出了你想要调用的功能类型——很明显,Everybody::sayHello()调用Everybody的功能。不存在"多态性歧义"——不存在需要查找其功能以产生预期多态行为的未知类型。

因此,您不需要动态调度来解决这个问题,您可以简单地使用阴影-即使您不能将静态方法作为虚拟方法重载,您仍然可以通过阴影来重载它们,这是可以的,因为您指定了类型,因此您将获得正确的版本。

您可以手动隐藏静态方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Base {
  static string toWhom() { return""; }
  static void sayHi() { cout <<"Hello" + toWhom(); }
};

struct World : Base {
  static string toWhom() { return"World"; }
  static void sayHi() { cout <<"Hello" + toWhom(); }
};

struct Everyone : Base {
  static string toWhom() { return"Everyone"; }
  static void sayHi() { cout <<"Hello" + toWhom(); }
};

或者使用一个类模板来完成它,因此您只需隐藏"虚拟静态方法",模板将确保为以下对象调用静态方法的正确类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
template <typename T>
struct Base {
  static string toWhom() { return""; }
  static void sayHi() { cout <<"Hello" + T::toWhom(); }
};

struct World : Base<World> {
  static string toWhom() { return"World"; }
};

struct Everyone : Base<Everyone> {
  static string toWhom() { return"Everyone"; }
};

然后

1
2
Everybody::sayHello();
World::sayHello();

这两种解决方案都将产生预期的结果。实现这一目标根本不需要任何多态性。请注意,确实可以实现您想要的,但这只会让您有可能创建一个效率较低的解决方案——因为多态性既有内存又有CPU时间开销,C++是一个主要关注性能和效率的语言。因此,它不支持不需要的特性,因为它已经可以按您的要求执行,而且它将很快燃烧起来,因为这样的简单函数甚至不会被调用——它们将被内联。内联函数和调用虚拟方法(比如对于这样的小函数使用20x)之间存在巨大的性能差异,并且为了实现静态虚拟成员而添加另一个间接级别只会使其更糟。

我希望现在我已经给出了令人信服的答案,为什么这是不可能的C++,为什么它不需要,以及为什么它没有意义使它成为可能在特定的语言。你基本上想在一个不需要它的情况下使用多态性,为了使语言变得更简单——嗯,你不能同时拥有它,C++是很难的,因为它很快,就像更容易的语言都比C++慢。

最后——如果你觉得这是一个非常重要的语言特性——你可以向标准委员会请求这个特性;)


您可以使用CRTP静态地实现多态行为。例如,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
using namespace std;

template <typename Derived>
struct Base {
    static void sayHello() {
        cout <<"Hello," << Derived::toWhom() <<"!" << endl;
    }
};

struct World : public Base<World> {
    static string toWhom() {
        return"world";
    }
};

struct Everybody : public Base<Everybody> {
    static string toWhom() {
        return"everybody";
    }
};

int main() {
    World::sayHello();
    Everybody::sayHello();
    return 0;
}

如果你想了解更多关于CRTP的信息,有很多关于CRTP的详细问题和答案。


因为您要与Objective-C进行比较:在Objective-C中,类本身就是对象。在C++中,类是编译时构造;它们在运行时不存在。C++静态类方法只是普通的外部函数,有一些有趣的语法。因为没有与Objective-C不同的类对象,所以虚拟静态函数没有任何意义。


static成员函数不作用于任何特定对象。

virtual成员函数是指定行为(所调用函数的版本)取决于所操作对象的类型(例如,object->foo()执行的代码取决于object的实际类型)的方法。

这就是EDOCX1、1和EDCOX1(0)的两个概念是如何在C++中指定的(至少在这种情况下)。这两个概念相互排斥。

不可能有不作用于任何对象的运行时行为,这取决于它作用于的对象类型。这是一个合乎逻辑的谬论。