C++类实例中的静态成员方法调用

54

这里是一个小测试程序:

#include <iostream>

class Test
{
public:
    static void DoCrash(){ std::cout<< "TEST IT!"<< std::endl; }
};

int main()
{
    Test k;
    k.DoCrash(); // calling a static method like a member method...

    std::system("pause");

    return 0;
}

在VS2008 + SP1 (vc9)中,它可以编译通过:控制台只会显示“TEST IT!”。

据我所知,静态成员方法不应该在实例对象上被调用。

  1. 我错了吗?从标准的角度来看,这段代码是正确的吗?
  2. 如果是正确的,为什么呢?我找不到为什么允许这样做,或者也许是为了帮助在模板中使用“静态或非静态”方法?

MVC标签是什么意思? - Kiril
已解决:我可能当时混淆了msvc,我认为这可能是编译器问题。 - Klaim
4个回答

82

标准规定并不需要通过实例调用方法,但这并不意味着你不能这样做。甚至有一个使用它的示例:

C++03, 9.4 静态成员

类X的静态成员s可以使用限定符表达式X::s来引用;没有必要使用类成员访问语法(5.2.5)来引用静态成员。可以使用类成员访问语法引用静态成员,在这种情况下,对象表达式将被计算。

class process {
public:
   static void reschedule();
};

process& g();

void f()
{
   process::reschedule(); // OK: no object necessary             
   g().reschedule(); // g() is called
}

1
哇,我从来不知道这个。我猜被调用的函数仅基于表达式的编译时类型(而不是运行时类型)? - Seth Carnegie
1
@SethCarnegie:是的,它使用对象的编译时类型(或其引用),您无法将动态分派到静态成员方法。 - David Rodríguez - dribeas

19

静态函数不需要实例化对象即可调用,因此

k.DoCrash();

与...完全相同的行为

Test::DoCrash();

使用作用域解析运算符(::)来确定类中的静态函数。

请注意,在这两种情况下,编译器都不会将this指针放入堆栈中,因为静态函数不需要它。


2
我更倾向于说“将this指针作为参数传递”,而不是“将其放入堆栈中”。实际方式取决于特定平台的调用约定。但是,因为提到了静态方法的这个特性,所以加1分。 - Melebius
1
有一个区别:在 k.DoCrash() 中,前缀 k 被计算。如果 k 只是一个对象名称,那可能无关紧要,但它可能是一个函数调用或具有副作用的其他表达式:func().DoCrash() - Keith Thompson
@KeithThompson 哇,那看起来很奇怪。你的例子中func()是否一定会返回一个Test对象? - feng
1
@feng:是的,要使 func().DoCrash() 有效,func() 必须返回类型为 Test 的结果。或者你可以使用 arr[func()].DoCrash(),其中 arr 是一个 Test 对象数组,而 func() 是一个返回某个整数类型的函数。 - Keith Thompson

4

这在几种情况下可能会有用:

  • [你提到的“模板中的‘static或非static’方法:] 当很多类型可以被指定给一个模板时,模板想要调用成员函数:提供静态函数的类型可以使用与成员函数相同的符号来调用 - 前者可能更高效(不需要传递/绑定this指针),而后者允许多态(virtual)分派和使用成员数据。

  • 最小化代码维护

    • 如果一个函数从需要特定于实例的数据发展到不需要它 - 因此被设置为static以便轻松无实例使用并防止意外使用实例数据 - 所有现有客户端使用点都不需要劳动更新。

    • 如果类型已更改,则var.f()调用将继续使用var类型的函数,而Type :: f()可能需要手动更正。

  • 当您有一个表达式或函数调用返回一个值并且想要调用(潜在或始终)static函数时,.符号可能会防止您需要使用decltype或支持模板来访问类型,以便您可以使用::符号。

  • 有时变量名只是更短,更方便,或以更自我说明的方式命名。


1
在至少 MSVC 2013 编译器上,通过实例调用静态方法是可行的,但如果变量除了调用静态方法之外没有被使用,会生成一个编译器警告关于未引用的变量。 - abelenky

2

静态方法也可以使用类的对象调用,就像在Java中一样。然而,你不应该这样做。使用作用域操作符如 Test::DoCrash();。也许你会想到命名空间:

namespace Test {
    void DoCrash() {
        std::cout << "Crashed!!" << std::endl;
    }
};

如果没有使用using directive/declaration将函数显式导入调用者的范围,那么只能从该命名空间外部通过Test::DoCrash();来调用。


2
是的,我知道我应该这样做,这就是为什么我问为什么另一种方式(像成员一样调用)被允许/不被禁止。 :) - Klaim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接