gpt4 book ai didi

c++ - NVI 和去虚拟化

转载 作者:塔克拉玛干 更新时间:2023-11-03 01:23:49 27 4
gpt4 key购买 nike

如果您使用的是 NVI,编译器可以去虚拟化函数调用吗?

一个例子:

#include <iostream>

class widget
{
public:
void foo() { bar(); }

private:
virtual void bar() = 0;
};

class gadget final : public widget
{
private:
void bar() override { std::cout << "gadget\n"; }
};

int main()
{
gadget g;
g.foo(); // HERE.
}

在标记的行中,编译器可以将对 bar 的调用去虚拟化吗?

最佳答案

鉴于 g 的动态类型恰好是 gadget,编译器可以在内联 后去虚拟化对 bar 的调用>foo,无论在 class gadget 声明中还是在 gadget::bar 声明中使用 final。我将分析这个不使用 iostreams 的类似程序,因为输出程序集更易于阅读:

class widget
{
public:
void foo() { bar(); }

private:
virtual void bar() = 0;
};

class gadget : public widget
{
void bar() override { ++counter; }
public:
int counter = 0;
};

int test1()
{
gadget g;
g.foo();
return g.counter;
}

int test2()
{
gadget g;
g.foo();
g.foo();
return g.counter;
}

int test3()
{
gadget g;
g.foo();
g.foo();
g.foo();
return g.counter;
}

int test4()
{
gadget g;
g.foo();
g.foo();
g.foo();
g.foo();
return g.counter;
}

int testloop(int n)
{
gadget g;
while(--n >= 0)
g.foo();
return g.counter;
}

我们可以通过检查输出程序集来确定去虚拟化是否成功:(GCC) , (clang) .两者都将 test 优化为等价于 return 1; - 调用被去虚拟化和内联,对象被消除。 Clang does the same for test2 through test4 - return 2;/3/4 分别 - 但 GCC seems to gradually lose track of the type information the more times it must perform the optimization .尽管成功地将 test1 优化为常量的返回值,但 test2 大致变为:

int test2() {
gadget g;
g.counter = 1;
g.gadget::bar();
return g.counter;
}

第一个调用已被去虚拟化并内联其效果 (g.counter = 1),但第二个调用仅被去虚拟化。在 test3 中添加额外调用会导致:

int test3() {
gadget g;
g.counter = 1;
g.gadget::bar();
g.bar();
return g.counter;
}

同样,第一个调用是完全内联的,第二个调用只是去虚拟化的,但第三个调用根本没有优化。这是来自虚拟表和间接函数调用的简单 Jane 加载。 test4 中的附加调用的结果相同:

int test4() {
gadget g;
g.counter = 1;
g.gadget::bar();
g.bar();
g.bar();
return g.counter;
}

值得注意的是,两个编译器都没有将 teSTLoop 的简单循环中的调用去虚拟化,它们都将其编译为等同于:

int testloop(int n) {
gadget g;
while(--n >= 0)
g.bar();
return g.counter;
}

甚至在每次迭代时从对象重新加载 vtable 指针。

final 标记添加到 class gadget 声明和 gadget::bar 定义中不会影响任一编译器生成的汇编输出(GCC) (clang) .

影响生成的程序集的是移除 NVI。这个程序:

class widget
{
public:
virtual void bar() = 0;
};

class gadget : public widget
{
public:
void bar() override { ++counter; }
int counter = 0;
};

int test1()
{
gadget g;
g.bar();
return g.counter;
}

int test2()
{
gadget g;
g.bar();
g.bar();
return g.counter;
}

int test3()
{
gadget g;
g.bar();
g.bar();
g.bar();
return g.counter;
}

int test4()
{
gadget g;
g.bar();
g.bar();
g.bar();
g.bar();
return g.counter;
}

int testloop(int n)
{
gadget g;
while(--n >= 0)
g.bar();
return g.counter;
}

由两个编译器 ( GCC ) ( clang ) 完全优化为:

int test1()
{ return 1; }

int test2()
{ return 2; }

int test3()
{ return 3; }

int test4()
{ return 4; }

int testloop(int n)
{ return n >= 0 ? n : 0; }

总而言之,尽管编译器可以将对bar 的调用去虚拟化,但在存在 NVI 的情况下它们可能并不总是这样做。优化的应用在当前的编译器中是不完善的。

关于c++ - NVI 和去虚拟化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18271676/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com