- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我观察到,当某个类的方法在 C++11 中被标记为 final
时,vtable 中没有查找来调用该方法,即使是从指针,至少是GCC 生产的组件。以这段代码为例:
class Base {
public:
Base() : retval(0) {}
virtual ~Base(){}
virtual int method() {
return retval;
}
protected:
uint32_t retval;
};
class DerivedFinal : public Base {
public:
int method() final {
return retval + 2;
}
};
int main() {
Base *bptr = new Base();
DerivedFinal *df = static_cast<DerivedFinal *>(bptr);
return df->method();
}
请注意,代码使用这样的返回值以使汇编代码易于阅读。
main 的程序集如下所示:
<+0>: push %rbp
<+1>: mov %rsp,%rbp
<+4>: push %rbx
<+5>: sub $0x18,%rsp
<+9>: mov $0x10,%edi
<+14>: callq 0x400750 <_Znwm@plt>
<+19>: mov %rax,%rbx
<+22>: mov %rbx,%rdi
<+25>: callq 0x400900 <_ZN4BaseC2Ev>
<+30>: mov %rbx,-0x18(%rbp)
<+34>: mov -0x18(%rbp),%rax
<+38>: mov %rax,-0x20(%rbp)
<+42>: mov -0x20(%rbp),%rax
<+46>: mov %rax,%rdi
<+49>: callq 0x400986 <_ZN12DerivedFinal6methodEv> // This is the method call
<+54>: add $0x18,%rsp
<+58>: pop %rbx
<+59>: pop %rbp
<+60>: retq
可以看出,调用该方法时没有进行任何 vtable 查找(如果该方法未标记为 final,则不会发生这种情况)。即使有继承自 DerivedFinal
的类,代码的行为方式也相同 我的问题是......这是标准行为吗?
编辑:让我们以不是未定义行为的方式重写代码,以明确显示方法是如何跳过 vtable 的,而在不是时查找它:
class Base {
public:
Base() : retval(0) {}
virtual ~Base(){}
virtual int method() {
return retval;
}
protected:
uint32_t retval;
};
class DerivedFinal : public Base {
public:
int method() final {
return retval + 2;
}
};
class DerivedNotFinal : public Base {
public:
int method() {
return retval + 3;
}
};
int main() {
DerivedFinal *df = new DerivedFinal();
DerivedNotFinal *dnf = new DerivedNotFinal();
int res_final = df->method();
int res_not_final = dnf->method();
return 0;
}
和程序集转储:
<+0>: push %rbp
<+1>: mov %rsp,%rbp
<+4>: push %rbx
<+5>: sub $0x28,%rsp
<+9>: mov $0x10,%edi
<+14>: callq 0x4007b0 <_Znwm@plt>
<+19>: mov %rax,%rbx
<+22>: movq $0x0,(%rbx)
<+29>: movl $0x0,0x8(%rbx)
<+36>: mov %rbx,%rdi
<+39>: callq 0x400a5c <_ZN12DerivedFinalC2Ev> // First ctor...
<+44>: mov %rbx,-0x18(%rbp)
<+48>: mov $0x10,%edi
<+53>: callq 0x4007b0 <_Znwm@plt>
<+58>: mov %rax,%rbx
<+61>: movq $0x0,(%rbx)
<+68>: movl $0x0,0x8(%rbx)
<+75>: mov %rbx,%rdi
<+78>: callq 0x400a82 <_ZN15DerivedNotFinalC2Ev> // Second ctor...
<+83>: mov %rbx,-0x20(%rbp)
<+87>: mov -0x18(%rbp),%rax
<+91>: mov %rax,%rdi
<+94>: callq 0x400a34 <_ZN12DerivedFinal6methodEv> // Call to DerivedFinal::method directly
<+99>: mov %eax,-0x24(%rbp) // Save result in stack
<+102>: mov -0x20(%rbp),%rax
<+106>: mov (%rax),%rax
<+109>: add $0x10,%rax
<+113>: mov (%rax),%rax
<+116>: mov -0x20(%rbp),%rdx
<+120>: mov %rdx,%rdi
<+123>: callq *%rax // Call to DerivedNotFinal::method via vtable (indirect call)
<+125>: mov %eax,-0x28(%rbp) // Save result in stack
<+128>: mov $0x0,%eax
<+133>: add $0x28,%rsp
<+137>: pop %rbx
<+138>: pop %rbp
<+139>: retq
通过这个例子,行为也很清楚。对 DerivedFinal::method 的调用不需要任何 vtable 查找,而对 DerivedNotFinal::method 的调用需要间接访问。在我看来,这种行为(带有 final 关键字)在某些性能关键的应用程序中是需要的,这就是为什么我问这种行为是否是标准的。
最佳答案
您的代码通过 static_cast
将指向不是 DerivedFinal
的对象的指针转换为 DerivedFinal *
来调用未定义的行为。
编译器有权做任何事情,包括让你的猫怀孕或召唤鼻恶魔。
在一个更明智的例子中,标记为 final
的方法不能在进一步的派生类中被覆盖,因此编译器可能 - 一个好的优化编译器应该 - 将调用去虚拟化。
关于c++ - 虚方法和虚表查找的最终说明符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28854132/
我得到以下声明: // file MadaPacket.h class MadaPacket { // .... public: inline static bool word_is_header
目录 1.语法 2.关键词decltype 1.语法 decltype ( 实体 ) (1) (C++11 起) decltype
由于某些原因,我一直认为演绎指南必须相同noexcept -它们所引用的构造函数的性质。例如: template struct clazz { clazz(const T &) noexcep
我不确定成员 var isMouseOverYard 的正确访问说明符。在代码片段中,我没有从 House 继承的计划。选项 1 与基类更一致(如果我要从任一类继承,我可以检查鼠标是否在对象/院子上)
我可以声明 foo(const T& var) 这样我就知道 var 不会被改变。 指针的等效格式为 foo(const T* var)? 过去我尝试过那些,与 iterator/const_iter
我已经为这个问题搜索了几个小时,但仍然无法解决。 #include using namespace std; enum color { brown, green, orange, red, yell
我有用户定义的数据类型 typedef Unsigned int8 COMMAND_TYPE[6]; 现在我有类似的功能 ConnectCommand(COMMAND_TYPE const comm
说明符 %[^s] 有什么用? s 是一个变量。 在什么情况下我可以使用这个说明符? 最佳答案 scanf 的 %[ 格式说明符将匹配一系列字符,这些字符与 [ 和 ]< 之间列出的字符相匹配。如果第
#include int main() { char a[8]; printf("%d\n",a) ; return 0; } 对于上面的代码,输出是这
很抱歉这个“另一个”sscanf 问题,但我无法通过实验找到任何解决方案。 这是一个字符串,我想解析并提取 2 个由“:”分隔的子字符串: char *str = "tag:R123:P1234";
所以我在维基百科的一篇文章(粗略翻译)中遇到了以下定义: Modifier (programming) - element of source code being a phrase of given
[basic.link]/6 (我的重点): The name of a function declared in block scope and the name of a variable dec
我正在尝试定义我自己的数据类型(称为 sfloat),它类似于 float ,但使用不同数量的尾数位和指数位以更好地适应我的数据范围和精度。目标是定义一种新的数据类型,可以替代现有应用程序中的 flo
请看下面的代码: #include struct A { A(int, int) {} }; struct tag {}; template struct is_noexcept { st
如果这是一个无知的问题,请原谅我,但我仍在思考何时以及如何使用 constexpr 说明符。 (使用 msvc 14 编译)。我正在研究一个简单的基类,它允许您将任意对象包装到“constexpr 对
考虑以下函数: // Declaration in the .h file class MyClass { template void function(T&& x) const; }; /
以下面的示例代码为例: void test(const Item& item = Item()) { ... } 假设一旦 item 被传递给函数,this 就不能抛出。 问题是:函数应该标记为
我听说 noexcept 关键字更像是“它永远不应该抛出异常”而不是“它不会”。 如果我不确定是否抛出异常,我认为使用 noexcept 关键字不是很好,但是 noexcept 关键字有时
最近,我在阅读API of boost::optional 时发现: T const& operator *() const& ; T& operator *() & ; T&&
如果覆盖 ToString在一个类型中 type TestMe ()= override __.ToString() = null 然后我通过 "%A" 输出它说明符 printfn "*%A
我是一名优秀的程序员,十分优秀!