如何确定一个C++类是否有虚函数表?

14
我的一个朋友今天早些时候向我发出了以下挑战:
给定以下代码,请提出 OBJECT_HAS_VTABLE 的一种实现,使程序打印 "AnObject has a vtable = 0, AnObjectWithVTable has a vtable = 1"。
class AnObject
{
    int m_a;
    void DoSomething() {}

public: 
    AnObject() {m_a = 0;}
};

class AnObjectWithVTable
{
    int m_b;
    virtual void DoStuff() { }

public: 
    AnObjectWithVTable() {m_b = 0;}
};

void main()
{
    printf("AnObject has a vtable = %i, AnObjectWithVTable has a vtable = %i\n",
           OBJECT_HAS_VTABLE(AnObject),
           OBJECT_HAS_VTABLE(AnObjectWithVTable));
}

我想出了以下解决方案,我认为它相当不错:

template <typename T>
bool objectHasVtable()
{
    class __derived : public T {};
    T t;
    __derived d;

    void *vptrT=*((void **)&t);
    void *vptrDerived=*((void **)&d);

    return vptrT != vptrDerived;
}

#define OBJECT_HAS_VTABLE(T) objectHasVtable<T>()

有没有更好的解决方案?

编辑

解决方案不必在所有编译器上通用。它可以在gcc、g++、MSVC上运行... 只需指定您的解决方案已知在哪个编译器上有效即可。我的解决方案适用于MSVC 2010。


4
通常情况下,你不能这样做,因为C++标准并没有规定虚函数表。你是在询问如何测试一个类是否有虚函数吗? - user2100815
我想问一下,你对挑战中提出的问题有什么解决方案。基本上,你能否确定一个类是否具有虚函数表。如果你认为使用gcc / g++不可能实现,我可以将问题限制在MS VC++上。 - joce
3
为什么我想要这样做?这不是一个编程竞赛网站。 - user2100815
这只是一个挑战,除了看看你能否做到,没有其他意义。 - joce
@unapersson:鉴于我在SO上寻找答案并没有找到,鉴于我在谷歌上搜索并没有找到现成的烹饪书答案,我认为它在这里有其存在的价值,因为它非常有启发性。我不知道人们何时需要它,但当他们需要时,他们现在可以在SO上找到答案了。 - joce
3个回答

19

标准方法是使用 C++11/C++03 TR1/Boost 中的 std::is_polymorphic,以确定一个类(及其基类)是否包含任何虚成员。

#include <type_traits>
#define OBJECT_HAS_VTABLE(T) (std::is_polymorphic<T>::value)

如果实现使用另一种方法来实现多态行为会怎样?开个玩笑。 - Martin York
此外,将Boost作为标准调用有些不妥。正如Martin所建议的那样,它并没有实际测试虚函数表(vtable)。 - user2100815
4
"type_traits" 是 TR1 和 C++0x 的一部分。后两者 都是 标准。 - kennytm
3
根据标准,std::is_polymorphic只能检测虚函数,但是即使没有虚函数,在使用虚继承时也会生成vtable。至少在GCC 4.8.5中是这样的。这个答案是不准确的。 - Kelvin Hu

11

为了完整起见,这是我朋友刚刚发给我的答案。从外观上看,它可能类似于TR1的实现方式(尽管我自己没有查看过代码)。

template<class T>
class HasVTable
{
public :
    class Derived : public T
    {
        virtual void _force_the_vtable(){}
    };
    enum { Value = (sizeof(T) == sizeof(Derived)) };
};

#define OBJECT_HAS_VTABLE(type) HasVTable<type>::Value

1
+1,好答案。我已经纠正了你代码中的一些语法错误。 - iammilind
1
请注意,如果type从其基类虚拟继承但不包含任何虚函数,则仍将返回答案为“true”。 - iammilind
我在阅读问题后思考了一下,但是无法弄清楚...那个答案很好,真的让我想自己扇耳光... - Mark K Cowan
我认为这个答案应该被采纳,它不仅可以检测虚函数,还可以检测虚继承。 - Kelvin Hu
@iammilind 我认为这种行为是正确的,因为虚继承也会产生vtable,而问题是关于如何检测vtable,而不是虚函数。 - Kelvin Hu
1
这个解决方案不适用于“final”类。对于“struct alignas(8) C {};”,它也会给出错误的答案。 - Maxim Egorushkin

3
你可以使用以下C++属性:
  1. dynamic_cast 在编译时无法转换非多态类,该失败可与SFINAE一起使用。
  2. dynamic_cast<void*> 是一个有效的转换,返回完整的多态对象的地址。
因此,在C++11中:
#include <iostream>
#include <type_traits>

template<class T>
auto is_polymorphic2_test(T* p) -> decltype(dynamic_cast<void*>(p), std::true_type{});

template<class T>
auto is_polymorphic2_test(...) -> std::false_type;

template<class T>
using is_polymorphic2 = decltype(is_polymorphic2_test<T>(static_cast<T*>(0)));

struct A {};
struct B { virtual ~B(); };

int main() {
    std::cout << is_polymorphic2<A>::value << '\n'; // Outputs 0.
    std::cout << is_polymorphic2<B>::value << '\n'; // Outputs 1.
}

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