Possible Duplicate:
C++ Static member method call on class instance
今天,我发现我有很长一段时间(我的意思是说,长达二十年),认为C++中的非法行为实际上是合法的。也就是说,调用一个静态成员函数,就好像它属于一个单独的对象一样。例如:
1 2 3 4 5 6 7 8 9 10
| struct Foo
{
static void bar() { cout <<"Whatever."; }
};
void caller()
{
Foo foo;
foo.bar(); // Legal -- what?
} |
我通常看到静态成员函数被严格地用"范围解析语法"调用,因此:
这是有意义的,因为静态成员函数与类的任何特定实例都没有关联,因此我们不希望在语法上将特定实例"附加"到函数调用。
然而,今天我发现GCC4.2、GCC4.7.1和CLANG 3.1(作为编译器的随机抽样)接受以前的语法,以及:
1 2
| Foo* foo = new Foo;
foo->bar(); |
在我的特殊情况下,这个表达式的合法性导致了一个运行时错误,这使我确信这个语法的特殊性不仅仅是学术上的兴趣,它还有实际的后果。
为什么C++允许静态成员函数被调用,就好像它们是单个对象的直接成员,也就是说,使用。或者->附加到对象实例的语法?
- 嗯,为什么不呢?区别在于作为隐式参数传递的实例是否与成员函数相同。函数指针本身可以通过任何一种方式被编译器访问。
- "在我的特殊情况下,这个表达式的合法性导致了一个运行时错误",您能在此基础上进行扩展吗?
- 我记得当我这样做时,当函数在.h中声明并在.cpp中定义时,会得到一个编译器错误。
- 在SO[此处](stackoverflow.com/questions/3255555/&hellip;)上已经有了一个类似的问题,并提供了一些很好的信息。
- 嗯,和第一个答案一样。但我不认为"因为圣经是这么说的"是一个足够的答案。
- 是的,@luchiangrigore,我会试试,但有点复杂。基本上,它出现在重构过程中。我有一个类以前提供了一个非静态的load()函数。稍后,我将该函数重构为静态函数,并返回一个指向新创建对象的指针,基本上,我将一个处理现有对象的加载函数转换为一个创建和加载该对象的工厂函数。(休息后有更多…)
- …然后我开始调整呼叫者以适应这个功能,使他们能够跟上程序。但我错过了一个案例,所以特定的调用者没有存储结果指针,这导致了一个错误。错误的调用者看起来像:Foo* foo = new Foo; foo->load();,但需要新的语义,Foo* foo = Foo::load();。所以我有点困惑,惊讶于编译器没有帮助我注意到语义上的巨大变化。这就是问题所在。
- 是的,我不认为圣经是一个很好的参考(基于信仰)。但是基于"标准"的答案是这个网站的最终目标。网站的设计并不是为了回答"为什么"这样的问题(因为这纯粹是猜测,因此相信不是事实)。我们需要的是事实而不是信仰。
- 您可能有一个具有属性大小、颜色、默认大小和默认颜色的实例的类。除了defaultsize和defaultcolour实际上不需要对象,因为它对所有对象都是相同的。您可以创建defaultsize和defaultcolour类方法,但仍然像实例方法一样调用它们。
在C++第288页的设计和演进中,Bjarne Stroustrup提到在静态成员函数之前的日子里,程序员使用像((X*)0)->f()那样的HAKS来调用不需要对象的成员函数。我的猜测是,当静态成员函数被添加到语言中时,允许通过->进行访问,这样具有类似代码的程序员就可以将f更改为static,而不必搜索和更改它的每一种使用。
大概是这样,您可以在不知道某个类的类型但编译器知道的地方调用它。
假设我有很多类,每个类都有一个返回类名的静态成员:
1 2 3 4 5 6 7 8 9
| class Foo
{
static const char* ClassName() { return"Foo"; }
};
class Bar
{
static const char* ClassName() { return"Bar"; }
}; |
然后在我的代码中,我可以做如下的事情:
1 2 3 4
| Foo foo;
printf("This is a %s
", foo.ClassName() ); |
不用担心随时知道我的物品的种类。例如,在编写模板时,这将非常方便。
- 型这个答案开始吸引人了。但我不认为在实践中这会有什么好处。(也许你可以考虑一个案例。)我认为,正如你所提到的,模板是最好的用例。因此,要调整您的第二个代码块:template void caller( T& t ) { printf("This is a %s
", t.ClassName(); }可以工作。但是,那么,printf("This is a %s
", T::ClassName() );会是什么样的呢?后者什么时候会变得混乱,更不容易接近,而前者不是呢?
- 型@老调查员:考虑这样一个情况,您更改了上面的代码,使Foo foo;现在是Bar foo;。如果您使用foo.ClassName(),这是您需要做的唯一更改。如果您使用了Foo::ClassName(),则需要记住进行2次更改。很好,有少量变化。对于sizeof foo和sizeof foo,typedef和许多其他只想声明一次的声明也是如此。
- 型@oldpeculier,当Foo::ClassName是静态的,但Bar::ClassName不是静态的,实际上它可能是虚拟的时,考虑您的模板示例。
- 型@Tinman说得对。看起来至少有点有用。
- 型@Markransom现在很有趣。是的,我可以看出这是非常有用的。这是迄今为止我听到的最令人信服的理论基础(IMO)。
- @oldpeculier:另一种情况是当类型不可获得时,例如函数的返回类型,因为在c11的decltype之前,语言不支持获取返回类型。它允许add(a,b).ClassName()而不必使用模板函数编写样板代码,模板函数的唯一目的是推导返回类型并对类型getClassName((add(a,b))调用静态ClassName方法。
- 这个答案应该集中在模板方面。实际上,如果两个方法都是静态的,那么所给出的示例可以简单地替换为对foo::ClassName()的调用,然后在顶部使用类似using foo = Foo;的调用,并以相同的方式工作。的实用性实际上是在对模板参数调用方法时,您不知道该方法是否是静态的。
从C++(PDF)的演变,第8节。静态成员函数:
...It was also observed that nonportable code, such as
was used to simulate static member functions.
所以我的猜测是(基于几乎所有其他奇怪的语法事物的基本原理模式),当您只有一个类型来提供与已建立但已损坏的习惯用法的向后兼容性时,它们允许调用静态成员函数。
就像这样,因为标准规定它就是这样工作的。N3290第9.4条规定:
A static member s of class X may be referred to using the qualified-id
expression X::s; it is not necessary to use the class member access
syntax (5.2.5) to refer to a static member. A static member may be
referred to using the class member access syntax, in which case the
object expression is evaluated. [ Example:
1 2 3 4 5 6 7 8 9 10
| struct process {
static void reschedule();
};
process& g();
void f() {
process::reschedule(); // OK: no object necessary
g().reschedule(); // g() is called
} |
end example ]
- 型但这并不能回答这样写标准"为什么"的问题。
- 型@拉斐尔巴普蒂斯塔——回答这个问题是猜测。我怀疑"这是一个静态函数"被认为是一个实现细节,与使用它的人无关。
- 型哦,凯。但是"因为圣经是这样说的",在软件工程的案例中并不是一个足够的答案。圣经为什么这么说?为什么标准委员会认为这是一件可取的事情?由于对象被忽略(除了它的类型),并且规则改变了对象的含义,所以从本质上来说,这似乎令人困惑。和->。
- 型我建议你回答。柔印的评论是另一个很好的理由。
- 型@我认为唯一能"正确"解决这些问题的方法就是使用规范。C++是C++,因为它是。不需要其他原因。当然,一份详细说明语言设计原因的参考文献会给出"为什么?"但这些基本原理信息似乎经常丢失。也许有一句话埋在纸上或采访稿的某个地方。
- 型"为什么"的答案可能是因为这是一个安全的扩展,它增加了一点灵活性,而不会有破坏事物的危险。
- 型对于一个被用作从属名称的基类,您可以调用this->foo();作为Base::foo();的替代方法,而Base::foo();可能更清楚,也可能不清楚。
- 型对于C++标准(与C99不同)没有任何理由,所以标准的特定部分通常没有解释。
- 型@罗马纳。另一方面,这一特性在爪哇和C.~(EDCOX1)2的情况下是不赞成的,尤其是在它休眠当前线程而不是"MyType"的情况下,因为它实际上在调用EDCOX1(3)方法。
- 型我想我感觉比C++语言设计者和古鲁更像你们中的一些人。我发现我通常会摸索和喜欢他们所做的决定(即使当他们很痛苦的时候,他们通常都是有道理的)。我觉得有能力影响他们的决定。总的来说,我喜欢知道我使用的工具背后的原因是它们的方式,特别是当原因令人惊讶或不明显时。我真的不相信这一点,"哦,标准是难以理解的,让我们只是服从它的神圣方式"的方法。没有冒犯-我夸大了我在这里感受到的哲学。
如果你不赞同"因为标准是这样说的"因果关系学派,我也建议静态方法已经足够成熟,可以追溯到人们真正担心将this参数传递给函数调用所带来的额外开销的时候,因此将纯函数"静态"作为优化可能是1中最流行的方法。985。