实施成员函数时的额外开销强制执行

3

我有一个 Base 类和一个 Derived 类。 Base 类的唯一目标是确保 Derived 实现了一个成员函数。

struct Base
{
    virtual void f() = 0;
};

struct Derived : Base
{
    void f() override final {}
};

我不会对这个类进行多态处理,也就是说,我只是像这样在栈上实例化Derived类型的对象:

Derived obj;

我需要进行数百万次操作。

编辑:同时只有几个实例存在(不会导致堆栈溢出)。

在这里是否创建了 vtable(我猜是在编译时)?如果我不使用它,那么是否对我有影响(或者我是否以某种方式使用它)?使用这种设计是否有任何额外的开销需要考虑?也许有其他方法可以确保编译器在 Derived 没有实现 f() 时发出警告?


这个回答解决了你的问题吗?C++中final关键字用于优化吗? - Moia
1
为什么不直接在Base中省略f()呢?如果Derived没有实现f(),那么obj.f()将无法编译通过。 - Evg
2
您可以检查 Derived 的大小。在我的情况下,它是8个字节,这意味着有一个虚表指针。_也许还有另一种方法可以确保编译器在 Derived 没有实现 f() 时发出警告吗?_当您为任何 Derived 对象调用 f 而没有提供 f 时,编译将失败。还请参阅:是否可能编写一个模板来检查函数的存在?。您可以编写一个元函数来检查 f 的存在,并使用它,例如使用静态断言进行更好的诊断。 - Daniel Langr
1
如果您不使用类的多态性,可以使用CRTP模式,它a)无需额外的奇怪传递和口哨即可确保实现方法的需要b)不使用vtable并且可能会导致优化调用。 - Swift - Friday Pie
@Evg 很好的观点,但是想象一下这段代码在一个库中并且只从另一个库中调用。 - mfnx
显示剩余3条评论
1个回答

3

这里创建了一个虚函数表吗?

是的,因为你有虚成员函数。

如果我不使用它,那么虚函数表是否对我有影响?

即使你不使用它,它仍然会影响你的 Derived 结构体的大小增加。
在这里,你的 Derived 结构体大小为8。但是如果没有虚函数表,它将只有1。

也许有另一种方式可以确保编译器在 Derived 没有实现 f() 时发出警告?

老实说,我认为你使用 Base 作为接口来强制每个派生类实现 f() 函数的解决方案是完全可以的,因为这是使用接口的确切用例。


但是,如果Derived结构的大小是一个问题(因为您说您想实例化它数百万次),也许您会对std::is_member_function_pointer类型特征感兴趣。
我不知道您打算如何实例化Derived结构,因此我无法提供完全适合您需求的代码。
但是,我正在考虑的想法等同于以下示例(通用示例):
 #include <type_traits>

template <typename T>
void instantiate_a_lot_of_times(std::size_t nb_times)
{
    // Check if the f() member function exists
    static_assert(std::is_member_function_pointer<decltype(&T::f)>::value, "Error: The T::f() member function must be defined");

    for(std::size_t i = 0; i < nb_times; ++i)
    {
        T t;
        // Do something with t
    }
}

但要记住,这种方法的缺点是会延迟检查。
当遇到结构定义时,编译不会失败,而是在评估static_assert时失败。

我在我的问题中添加了一个编辑:同一时间只有几个实例存在。 - mfnx
我认为在性能方面,在栈上分配 8 或 1 并不重要,对吗? - mfnx
@mfnx 在这种情况下,我认为你的方法应该是好的。我进行了一些基准测试,使用了 10e7 次迭代(1 次迭代:实例化 3 个 Derived 对象并在每个实例上调用 f())。无论是否使用虚函数表(大小为 8 或 1),我都没有看到任何显着的时间差异(在我的机器上只有几百分之一秒,这台机器不是很强大)。 - Fareanor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接