考虑以下 C++03 标准的代码:
struct A
{
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void foo() { cout << "foo()" << endl; }
};
int main(int argc, char** argv)
{
A* pa = (A*)0;
pa->foo();
return 0;
}
编译和运行得很好。
出现了以下问题:
1)为什么程序会因为方法的虚拟版本而foo崩溃?
2)如何使用C++11安全调用非静态方法foo,使方法foo有效传递this,从而避免UB使用时?
1)这是未定义的行为。为什么您编写的内容有效 - 必须在编译器的实现及其创建的代码中寻找答案。提示 - 在一种情况下,在类的主体中声明了一个隐式内联函数。另一方面,当函数是虚函数时,需要有一个初始化的虚表,因为函数的地址存储在其中。该标准不描述实现并将平台视为抽象的冯诺依曼机。
结果是正确的操作、内存泄漏、完全擦除硬盘驱动器、熔化的处理器,还是从你的鼻子里飞出并开始嘲笑你的恶魔——都没有关系,因为它没有被标准定义。它可以在一个编译器中以这种方式运行,而在另一个编译器中以不同的方式运行,它可以根据编译器的版本而变化,它可以随着每次编译而变化,随着月相的变化,在上级在场的情况下,以及取决于你的心情和上个晴天飞过处理器的中微子数量。或者也许不会改变。
2)原则上,如果类的实例已经存在,则有一种合法的调用方式。你仍然需要传递一个指向它的指针。你可以用 std::bind 做到这一点。此函数将返回一个可调用对象,该对象将保存绑定到对象实例的方法。如果没有对象,那么什么都没有,如果你不违反标准。
唯一可以使用空指针的是确定字段的偏移量,但此技术被标准化为 offsetof(在 中定义
<cstddef>),其记录可能再次取决于编译器。也许您的问题是从静态方法调用非静态方法。当 this 指针已知时(例如,可以传递给静态成员)?这是一个在英文网站上讨论过的例子:
在这种情况下,使用了一个非静态指针,它在构造函数中被初始化,这就是奇怪的构造 (t->*(t->function))() 出现的原因;这个例子是在不创建子类的情况下模拟了对虚表的操作,从这段代码中可以明显看出你在描述虚表的功能时落在了哪里。
如您所知,您的代码包含未定义的行为。不允许在空指针上调用方法。
如果编译器将非虚拟方法调用编译
this为作为隐藏参数传递的静态定义方法调用,并且您没有在方法代码中取消引用this,并且优化器决定不使用显式 UB,则这可能不会下降。同样,如果优化器对您的代码进行流分析,发现它实际上知道参数的确切类型,将其去虚拟化并静态调用该方法,则对虚拟方法的调用可能不会崩溃。或者,如果优化器决定通过虚拟方法表调用,它可能会下降。
UB,就是,你可能幸运,也可能不幸运。
按照标准,没办法。非静态方法只有在您手中有一个对象(或指向对象的指针)时才能被调用,该对象的类型与所需的类型匹配或从中派生。你不能
this明确地“传递你的”。而既然有了对象,就已经构造好了。通过指向另一个对象的指针或空指针进行的调用是 UB。不要这样做。
另一方面,如果您正在为特定编译器编写代码,您有时可以要求它吞下调用
nullptr并在不存在this. 但这是标准范围之外的灰色地带。除非真的有必要,否则不要这样做。让我这样说吧——如果一个方法可以在不创建类实例的情况下被调用,那么根据定义,它就是静态的。
正如 Occam 所说,不要在不必要的情况下增加实体,并使这种方法静态化。
否则,您的项目类似于问题 - 如何让躯干在没有头的情况下移动。他们说这种情况会发生……但非常不可靠,而且不会持续很长时间。
在解决方法中,我只想到了 crtp;示例代码如下所示:
至于安全性,我希望它是明确的,因为。取消引用空指针就像纯水的 UB :)
PS 这篇文章是我的恕我直言,我没有在失望中检查它。从编译器的角度来看,一个紫色的静态函数或非静态函数,调用一个非静态函数很可能看起来像这样:
A::foo(&a);因此,你可以在那里传递任何 &a,一切都会起作用,直到需要这个,这个是 UB 所在的位置。那些。如果非静态成员函数 foo() 实现静态行为(您可以毫无问题地为它添加前缀static),那么视图构造((A*)nullptr)->foo();是可行的,尽管它在法律上是 UB。