【问题标题】:C++: Can virtual inheritance be detected at compile time?C++:可以在编译时检测到虚拟继承吗?
【发布时间】:2010-05-23 22:41:15
【问题描述】:

我想在编译时确定是否可以在没有 dynamic_cast 的情况下从指向 Base 的指针转换指向 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() 技巧可以做什么感到困惑?

【问题讨论】:

  • 我认为如果Base 只是Derived 的虚拟基类的超类,这应该与static_cast 一起使用。至少我找不到标准另有说明。您可以使用boost::is_virtual_base_of 来检查其他情况。
  • 我几乎可以想到一些方法,但它们很难看。如果你能给我一些关于你想用这个做什么的信息,我可能会想出一些东西。
  • 如果 Base 是 Derived 的虚拟基的超类,gcc 会产生错误(例如):错误:无法从基 'osg::Referenced' 转换为派生类型 'osgViewer: :ViewerBase' via virtual base 'osg::Object' 使用虚拟基类是使用指向基的指针实现的心理模型,错误对我来说很有意义:static_cast 可以毫无问题地将“超级基”转换为虚拟基,但是在将指针转换为派生类型时情况相同。
  • @Tim 绝对不在这里:codepad.org/9cf7ZWK2
  • @litb 我的情况有点不同:codepad.org/MMlYcvJw。正如我所怀疑的那样,这不会编译。

标签: c++ templates


【解决方案1】:

我曾经遇到过同样的问题。不幸的是,我不太确定虚拟问题。但是:Boost 有一个名为is_base_of 的类(参见here),它可以让你做某事。像下面这样

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

此外,Boost 的type_traits 中有一个类is_virtual_base_of,也许这就是你要找的。​​p>

【讨论】:

  • (-1) 问题没有提到 boost 库。答案不应该需要新的第三方库。
  • @Mike:你是从哪里得到这个想法的? C++ 的主要优势之一是大量可用的高质量库。看看其他一些 C++ 问题,你会发现左右两边都建议了 boost。如果问题说他们不能使用第三方库,那就另当别论了。
  • 即使你不想要这个库,你至少可以看看 boost 看看他们是如何实现它的。
  • @Mike。对于各种编译器的一些边缘情况,将自己的代码作为 boost 处理并不是一个好主意。但是,如果您想了解如何推出自己的产品,我已经在stackoverflow.com/questions/2893791/…的回答中概述了它@
  • @ZXX:虽然你的评论很有趣,但 Boost 既不是一个大库也不是一个“框架”。它是图书馆的集合。它们中的大多数都非常轻量级:事实上,甚至不需要将静态库导入到您自己的项目中,更不用说发布动态库了:它们是header-only。它没有比这更轻量级的了。
【解决方案2】:

这里有一个解决方案,可以根据类是否是另一个类的子类来重定向编译器以执行某些操作。

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"
}

如果你想知道 boost 是如何做到的。如果你有课 基类和类 Derived 那么以下成立。

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 对其进行测试。

【讨论】:

  • 我已经投票了,因为其他解决相同子问题的答案都被投票了,但这并不能解决具体问题:检测继承是否是虚拟的——也就是说,不仅是其他但关系是虚拟的
  • 编辑代码以使用 is_virtual_base_of boost.org/doc/libs/1_43_0/libs/type_traits/doc/html/…
  • Boost 的解决方案是一个非常聪明的解决方案,现在这个答案似乎是对 OPs 问题的完整答案。
  • 感谢您对 Boost 的 is_virtual_base_of 工作原理的解释。
【解决方案3】:

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

其次,sizeof 技巧必须使用 0 的强制转换,而不是实际的指针或对象——这保证了零开销,并且在你做对之前它不会遵守。

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

4th - 你有什么重要的理由来尝试使用 static_cast 而不是直接在这里进行政治正确吗?编译器将始终从中推断出您正在寻找更多抱怨,在这种情况下您肯定不是。

顺便说一句,您不需要从 Alexandrescu 获取完整代码 - 只需获取核心技术,基本上就是:

sizeof(*((T*)0))

Alexandrescu 非常擅长在戏法后清理。

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

一旦你有了它,你需要准确地和积极地定义你的陈述“如果一个指向 Derived 的指针可以从一个指向 Base 的指针转换而不使用 dynamic_cast”实际上意味着在类关系方面 - 只是说“没有运算符/函数 Q" 不能很好地定义问题,你无法解决你无法定义的问题 - 老实说:-)

因此,只需迈出编译的第一个干净步骤,然后尝试定义您提到的两种情况在现实中的不同之处 - 一种具有或做什么而另一种不会。

【讨论】:

  • +1,第 1 点和第 2 点肯定是正确的,我对第 3 点和第 4 点的意见留待以后有时间了解和测试它。对于如何准确且积极地声明从派生到基础的static_cast 将起作用,您有什么提示吗?
  • 不——这就是我问的原因:-) 我总是怀疑试图通过像 static_cast 这样的构造的结果来定义特征或关系。您需要先定义要查找的内容,然后可能会发现 static_cast 与在编译时确定特定特征无关,或者特征没有可观察到的效果,因此该特征无关紧要,或者仅在其他情况下才可观察也存在。
  • 旁注:还记得 static_cast 是如何拒绝虚拟基础的吗?虽然对于物理对象有充分的理由(如果没有等效的向上转换,则向下转换是不可能的)它不提供任何特征信息 - 它只是大喊“我无法确定”:-)
【解决方案4】:

你试过 Loki 的 SUPERSUBCLASS 吗?

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

【讨论】:

  • 我在 Modern C++ Design book 中查看了 SUPERSUBCLASS 的实现。我需要的不仅仅是超子类的测试;我想知道 super 是否可以使用 static_cast 向下转换(向上转换?:) 到 sub。
【解决方案5】:

一旦转换为基指针,你只能得到一个运行时错误(dynamic_cast)。您可以使用模板参数定义方法并使用模板特化获得编译错误。

【讨论】:

    【解决方案6】:

    在编译时有一个模板破解。

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

    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;
    };
    

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

    这种方式可能看起来不漂亮,但确实可以。

    【讨论】:

      【解决方案7】:

      如果你想在编译时知道你可以把派生类作为参数 但是,如果您唯一拥有的是 Base,那么您将无法知道它是否引用了任何 foo、bar 等类。此检查只能在指针转换为 Base 时进行。我认为这就是 dynamic_cast

      的全部目的

      【讨论】:

        【解决方案8】:

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

        【讨论】:

        • 一种在运行时确定对象动态类型的方法,即使用dynamic_cast。此外,reinterpret_cast 不会在这里做,因为多个基类的存在导致在继承层次结构中转换时需要调整地址。
        猜你喜欢
        • 2016-03-26
        • 2011-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-03-31
        相关资源
        最近更新 更多