C ++:可以在编译时检测虚拟继承吗?

时间:2010-05-23 22:41:16

标签: c++ templates

我想在编译时确定是否可以从没有dynamic_cast<>的指针转换为指向Derived的指针。这可能使用模板和元编程吗?这与确定Base是否是Derived的虚拟基类不完全相同,因为Base可能是Derived虚拟基类的超类。

谢谢, 蒂姆 更新: 我觉得这种方法很好:

#include <iostream>

using namespace std;

class Foo
{
};

class Bar : public Foo
{
};

class Baz : public virtual Foo
{
};

class Autre : public virtual Bar
{
};

typedef char Small;
class Big { char dummy[2]; };

template<typename B, typename D>
struct is_static_castable
{
    const B* foo;
    char bar[1];
    static Small test(char(*)[sizeof(static_cast<const D*>(foo)) == sizeof(const D*)]);
    static Big test(...);
    enum { value = (sizeof(test(&bar)) == sizeof(Small)) };
};

int main()
{

    cout << "Foo -> Bar: " << is_static_castable<Foo, Bar>::value << "\n";
    cout << "Foo -> Baz: " << is_static_castable<Foo, Baz>::value << "\n";
    cout << "Foo -> Autre: " << is_static_castable<Foo, Autre>::value << "\n";
}

但它不适用于gcc:

multi-fun.cpp: In instantiation of ‘is_static_castable<Foo, Baz>’:
multi-fun.cpp:38:   instantiated from here
multi-fun.cpp:29: error: cannot convert from base ‘Foo’ to derived type ‘Baz’ via virtual base ‘Foo’
multi-fun.cpp:29: error: array bound is not an integer constant
multi-fun.cpp: In instantiation of ‘is_static_castable<Foo, Autre>’:
multi-fun.cpp:39:   instantiated from here
multi-fun.cpp:29: error: cannot convert from base ‘Foo’ to derived type ‘Autre’ via virtual base ‘Bar’
multi-fun.cpp:29: error: array bound is not an integer constant

我对使用sizeof()技巧可以做些什么感到困惑?

8 个答案:

答案 0 :(得分:11)

我有同样的问题,一次。不幸的是,我不太确定虚拟问题。但是:Boost有一个名为is_base_of的类(见here),它可以让你做一些事情。如下

BOOST_STATIC_ASSERT((boost::is_base_of<Foo, Bar>::value));

此外,在Boost的is_virtual_base_of中有一个班级type_traits,也许这就是你要找的东西。

答案 1 :(得分:5)

这是一个解决方案,用于重定向编译器以执行某些操作,具体取决于该类是否是另一个的子类。

class A 
{};

class B : virtual public A
{};

class C : public A
{};

// Default template which will resolve for 
// all classes
template 
< typename T
, typename Enable = void 
>
struct FooTraits
{
    static void foo(){
        std::cout << "normal" << std::endl;
    }
};

// Specialized template which will resolve
// for all sub classes of A
template 
< typename T 
>
struct FooTraits 
    < T
    , typename boost::enable_if
         < boost::is_virtual_base_of< A, T>
         >::type
    >
{
    static void foo(){
        std::cout << "virtual base of A" << std::endl;
    }
};

int main(int argc, const char * argv[] ){
    FooTraits<C>::foo(); // prints "normal"
    FooTraits<B>::foo(); // prints "virtual base of A"
}

如果你想知道增强是如何做到的。如果你上课了 基础和类派生然后以下成立。

struct X : Derived, virtual Base 
{
   X();
   X(const X&);
   X& operator=(const X&);
   ~X()throw();
};

struct Y : Derived 
{
   Y();
   Y(const Y&);
   Y& operator=(const Y&);
   ~Y()throw();
};

bool is_virtual_base_of = (sizeof(X)==sizeof(Y)));

这是使用具有多重继承性的虚拟继承的技巧。多 来自同一虚拟基础的继承不会导致重复 虚拟基类,因此您可以使用sizeof。

进行测试

答案 2 :(得分:3)

首先,你的代码正在执行指针的sizeof而不是取消引用的指针,因此即使gcc没有抱怨它也不会起作用。

其次,sizeof技巧必须使用0的强制转换,而不是实际的指针或对象 - 这可以保证零开销,并且在你正确执行之前它不会编译。

第3,你需要声明2个模板化类或结构一个只从D派生,另一个派生自D和虚拟B,然后将0转换为它们的指针,取消引用它们然后取出sizeof。

第4页 - 你有没有什么大的理由试图用static_cast来政治上正确而不是直接投在这里?编译器总是会从中推断出你正在寻找更多的抱怨,在这种情况下你肯定不会。

顺便说一句,你不需要从Alexandrescu那里获取完整的代码 - 只需抓住基本的核心技术:

sizeof(*((T*)0))

Alexandrescu非常善于在一招之后进行清理。

哦,请记住,编译器不应该评估sizeof args或实例化未使用的模板化类和结构 - 所以如果它确实那么它是编译器错误,如果你强制它这样做那么它就是你的bug: - )

一旦你有了这个,你需要准确地和积极地定义你的语句“如果指向Derived的指针可以从没有dynamic_cast&lt;&gt;的指针转换为Base”实际上意味着在阶级关系方面 - 只是说“没有操作员/功能Q”并没有明确定义问题,你无法解决你无法定义的事情 - 诚实: - )

所以,只需采取第一个编译的清洁步骤,然后尝试定义您提到的两种情况在现实中会以哪种方式存在差异 - 另一种情况下或另一种情况不会发生的情况。

答案 3 :(得分:2)

你有没有尝试过Loki的SUPERSUBCLASS?

http://loki-lib.sourceforge.net/

答案 4 :(得分:1)

答案 5 :(得分:1)

在编译时有一个模板黑客可以做到。

首先,您需要创建一个这样的接口类:

template <typename T>
class SomeInterface
{
public:
    inline int getSomething() const;
};

template<typename T>
inline int SomeInterface<T>::getSomething() const
{
    return static_cast<T const*>(this)->T::getSomething();
}

这个想法是:this投射到T并使用相同的名称和相同的参数调用方法。 正如您所看到的,包装函数是内联的,因此在运行时不会出现性能或调用堆栈开销。

然后创建实现这个接口的类:

class SomeClass : public SomeInterface<SomeClass>
{
    friend class SomeInterface<SomeClass>;

public:
    int getSomething() const;
};

然后通常只添加派生方法的实现。

这种方式看起来可能并不漂亮,但完全符合这项工作。

答案 6 :(得分:0)

如果您想在编译时知道,可以将派生类作为参数 但是,如果你唯一的东西是Base那么你就不知道它是否指的是任何foo,bar等等。只有在指针转换为Base的情况下才能进行此检查。我认为这是dynamic_cast&lt;&gt;的全部目的。

答案 7 :(得分:-2)

这可能有点幼稚(我在C语言中比在C ++中强得多)所以我可能不明白你想要做什么,但是如果它是你正在谈论的指针,C风格强制转换(例如。(D *)foo)或C ++等效的reinterpret_cast。话虽如此,这可能非常危险,因为您没有任何运行时检查,因此需要确保您正在进入正确的类型。然后,如果你想要一个简单的方法来检查这是否是一个正确的假设,我们又回到原点。但是,您似乎正在尝试比较上面的指针,它们都是相同的(它们基本上是整数)。据我所知,没有办法在运行时在C ++中确定一个对象的类,包括sizeof,它在编译时工作。基本上,没有办法做你想做的事情(至少不是标准的C ++),但实际的演员不会导致任何问题,只是以不正确的方式使用新的指针。如果你绝对需要这个功能,你可能最好在你的基类中包含一个虚函数来报告它是什么类(最好是枚举值),并在你希望确定是否也可以投射的子类中重载它