为啥调用静态成员函数。或 -> 语法合法? [复制]
Posted
技术标签:
【中文标题】为啥调用静态成员函数。或 -> 语法合法? [复制]【英文标题】:Why is calling a static member function with . or -> syntax legal? [duplicate]为什么调用静态成员函数。或 -> 语法合法? [复制] 【发布时间】:2012-08-12 05:03:27 【问题描述】:可能重复:C++ Static member method call on class instance
今天我发现我长久以来(我的意思是长久——比如,二十年),在 C++ 中认为非法的东西实际上是合法的。即,调用静态成员函数,就好像它属于单个对象一样。例如:
struct Foo
static void bar() cout << "Whatever.";
;
void caller()
Foo foo;
foo.bar(); // Legal -- what?
我通常会看到严格使用“范围解析语法”调用静态成员函数,因此:
Foo::bar();
这是有道理的,因为静态成员函数不与类的任何特定实例相关联,因此我们不希望特定实例在语法上“附加”到函数调用。
然而我今天发现 GCC 4.2、GCC 4.7.1 和 Clang 3.1(作为编译器的随机抽样)接受前一种语法,并且:
Foo* foo = new Foo;
foo->bar();
在我的特殊情况下,这个表达式的合法性导致了一个运行时错误,这让我相信这个语法的特殊性不仅仅是学术上的兴趣——它还有实际的后果。
为什么 C++ 允许像调用单个对象的直接成员一样调用静态成员函数——也就是说,通过使用 .或 -> 附加到对象实例的语法?
【问题讨论】:
嗯,为什么不呢?不同之处在于实例作为隐式参数传递,与成员函数一样,甚至不传递。无论哪种方式,编译器都可以访问函数指针本身。 “在我的特殊情况下,这个表达式的合法性导致了运行时错误”你能详细说明一下吗? 我记得当函数在 .h 中声明并在 .cpp 中定义时,我执行此操作时遇到编译器错误 嗯,与这里的第一个答案相同的信息。但我不认为“因为圣经这么说”是一个充分的答案。 是的,我不认为圣经是一个很好的参考(它基于信仰)。但基于“标准”的答案是本站的最终目标。该网站并非旨在回答诸如“为什么”这样的问题(因为那将是纯粹的猜测,因此相信不是事实)。我们希望网站上的事实而不是信念。 【参考方案1】:之所以这样,是因为标准规定它是这样工作的。 n3290 § 9.4 规定:
类 X 的静态成员 s 可以使用qualified-id来引用 表达式
X::s;
不需要使用类成员访问 语法 (5.2.5) 来引用静态成员。静态成员可能是 引用使用类成员访问语法,在这种情况下 对象表达式被评估。 [示例:struct process static void reschedule(); ; process& g(); void f() process::reschedule(); // OK: no object necessary g().reschedule(); // g() is called
结束示例]
【讨论】:
@RafaelBaptista - 回答这将是猜测。我怀疑“这是一个静态函数”被认为是一个实现细节,与使用它的人无关。 哦,好。但是“因为圣经是这样说的”,在软件工程的情况下并不是一个充分的答案。为什么圣经这么说?为什么标准委员会认为这是一件可取的事情?这似乎本质上令人困惑,因为对象被忽略(除了它的类型)并且规则改变了 .和 ->。 @OldPeculier 我认为在 SO 上“正确”解决这些问题的唯一方法是使用规范。 C++ 是 C++,因为它是。不需要其他原因。当然,详细说明该语言的设计原因的参考资料会给出“为什么?”,但这种基本原理信息似乎经常丢失..也许有一个引用被埋没了在某处的一些论文或采访手稿中。 “为什么”的答案可能是因为这是一个安全的扩展,它增加了一点灵活性而不会有破坏的危险。 C++ 标准 (unlike for C99) 没有基本原理,因此标准的具体部分一般不作说明。【参考方案2】:大概是这样你就可以在你可能不知道某些东西的类类型但编译器知道的地方调用它。
假设我有一堆类,每个类都有一个返回类名的静态成员:
class Foo
static const char* ClassName() return "Foo";
;
class Bar
static const char* ClassName() return "Bar";
;
然后在我的代码中我可以做这样的事情:
Foo foo;
printf( "This is a %s\n", foo.ClassName() );
不必一直担心知道我的对象的类别。这在编写模板时会非常方便。
【讨论】:
这个答案开始有一些吸引力。然而,我认为这在实践中不会有任何好处。 (也许您可以想到一个案例。)我认为,正如您所提到的,模板将是最好的用例。所以要调整你的第二个代码块:template<typename T> void caller( T& t ) printf( "This is a %s\n", t.ClassName();
会起作用。但是,printf( "This is a %s\n", T::ClassName() );
也会如此,而后者什么时候会令人困惑,更不用说难以访问,而前者不是?
@OldPeculier:考虑这样一种情况,您将上面的代码更改为 Foo foo;
现在是 Bar foo;
。如果您使用foo.ClassName()
,这是您唯一需要做的更改。如果您使用过Foo::ClassName()
,您需要记住进行 2 处更改。只需少量更改即可。 sizeof foo
vs sizeof Foo
、typedefs 和许多其他你只想声明一次的声明也是如此。
@OldPeculier,当Foo::ClassName
是静态的但Bar::ClassName
不是静态的,实际上它可能是虚拟时,请考虑您的模板示例。
@tinman 好点。看起来至少有点用处。
@OldPeculier:另一种情况是无法获取类型,例如函数的返回类型,因为在 C11 的 decltype
之前,语言不支持获取返回类型。它允许add(a,b).ClassName()
而不是必须使用模板函数编写样板代码,模板函数的唯一目的是推断返回类型并在类型上调用静态ClassName
方法:getClassName((add(a,b))
。【参考方案3】:
如果您不订阅“因为标准这么说”的因果关系学派,我还建议静态方法已经足够老了,以至于人们真正担心通过this
带来的额外开销函数调用的参数,因此在 1985 年,将纯函数作为一种优化来“静态化”可能风靡一时。
【讨论】:
【参考方案4】:在 The Design and Evolution of C++ 的第 288 页,Bjarne Stroustrup 提到在静态成员函数之前的日子里,程序员使用像 ((X*)0)->f()
这样的黑客来调用不需要目的。我的猜测是,当静态成员函数被添加到语言中时,允许通过->
进行访问,这样使用类似代码的程序员就可以将f
更改为static
,而无需寻找并更改它的每次使用。
【讨论】:
+1 参考 [可能的] 设计师见解/决定。【参考方案5】:来自The Evolution of C++ (pdf), section 8. Static Member Functions:
...还观察到不可移植的代码,例如
((x*)0)->f();
用于模拟静态成员函数。
所以我的猜测是(基于几乎所有其他奇怪语法事物的基本原理模式)当您刚刚拥有提供与已建立但已损坏的习惯用法的向后兼容性的类型时,它们允许调用静态成员函数。
【讨论】:
现在this回答我开始实际购买了。谢谢。以上是关于为啥调用静态成员函数。或 -> 语法合法? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
C++中static函数类外定义的时候为啥不写static?