【问题标题】:What is the price of virtual inheritance?虚拟继承的价格是多少?
【发布时间】:2016-03-19 06:53:05
【问题描述】:

这似乎是一个基本问题,但我没有看到它被问到:

假设以下简单情况:

  • 没有虚拟成员。

  • 虚拟继承用于允许多个路径指向同一个基。

就访问最派生类的成员所需的时间而言,虚拟继承的代价是多少?特别是,如果有一个非零价格,它是否只与通过多条路径继承的成员或其他成员有关?

【问题讨论】:

  • 你的意思是“访问一个最派生类的成员,只给一个指向虚拟基础子对象的指针”?
  • 对虚拟表的每次查找都有一个非零价格。答案取决于实施。我猜这是一个 O(1) 哈希表。
  • 没有虚拟会员。成员被覆盖。虚拟继承用于允许到同一个基的多个路径。
  • 如果没有虚函数,就没有什么可以覆盖的。
  • 没有。覆盖是关于虚函数。如果没有虚函数,则无需覆盖任何内容。

标签: c++ inheritance virtual-inheritance


【解决方案1】:

通过额外的间接访问虚拟基类的数据成员。

【讨论】:

  • 啊哈。这意味着对派生类引入的新数据成员的访问不会通过虚拟表。这是正确的吗?
  • @AlwaysLearning 只有派生到虚基转换才需要使用vtable(或者MSVC++中的指针)。
【解决方案2】:

就访问最派生类的成员所需的时间而言,虚拟继承的价格是多少?

一个偏移查找和一个添加(2 条指令和一个内存提取)

特别是,如果存在非零价格,它是否仅与通过多个路径继承的成员有关,还是也与其他成员有关?

是的,即使这样也不总是。如果编译器有足够的信息证明不需要通过间接访问,则可以在编译时随意短路查找。

最好明确说明何时会出现这种情况。 ——尼可波拉斯

先生说得好。

这里有一个例子来证明这一点。使用 -O2 和 -S 选项进行编译以查看优化效果。

#include <memory>
#include <string>

enum class proof {
    base,
    derived
};

// volatile forces the compiler to actually perform reads and writes to _proof
// Without this, if the compiler can prove that there is no side-effect  of not performing the write,
// it can eliminate whole chunks of our test program!

volatile proof _proof;

struct base
{
    virtual void foo() const {
        _proof = proof::base;
    }

    virtual ~base() = default;
};

struct derived : base
{
    void foo() const override {
        _proof = proof::derived;
    }
};

// factory function
std::unique_ptr<base> make_base(const std::string&name)
{
    static const std::string _derived = "derived";

    // only create a derived if the specified string contains
    // "derived" - on my compiler this is enough to defeat the
    // optimiser

    if (name == _derived) {
        return std::make_unique<derived>();
    }
    else {
        return {};
    }
}

auto main() -> int
{
    // here the compiler is fully aware that p is pointing at a derived
    auto p = std::make_unique<derived>();

    // therefore the call to foo() is made directly (in fact, clang even inlines it)
    p->foo();

    // even here, the compiler 'knows' that b is pointing at a 'derived'
    // so the call to foo is made directly (and indeed on my compiler, completely
    // inlined)
    auto b = std::unique_ptr<base>(new derived);
    b->foo();

    // here we assign a derived to b via indirect construction through a string.
    // Unless the compiler is going to track this string and follow the logic in make_base
    // (and on my compiler it does not) this will prevent the virtual call to foo() from
    // being turned into a direct call.
    // Therefore, this call will be made via the virtual function table of *b
    b = make_base("derived");
    if (b) {
        b->foo();
    }

    return 0;
}

【讨论】:

  • @NicolBolas 添加了说明性示例。谢谢。
猜你喜欢
  • 2013-12-07
  • 2017-03-31
  • 2015-11-21
  • 1970-01-01
  • 2014-10-12
  • 1970-01-01
  • 1970-01-01
  • 2013-11-19
  • 1970-01-01
相关资源
最近更新 更多