【问题标题】:Determining inheritance relationships among type-erased classes确定类型擦除类之间的继承关系
【发布时间】:2013-05-31 08:29:11
【问题描述】:

我有一个典型的类型擦除设置:

struct TEBase
{
    virtual ~TEBase() {}
    // ...
};

template <typename T>
struct TEImpl : TEBase
{
    // ...
};

现在的问题是:给定这样的第二个类层次结构,

struct Foo { };
struct Bar : Foo { };

struct Unrelated { };

是否有可能给定TEBase * p,确定*p 的动态类型是否为TEImpl&lt;X&gt; 形式,其中X 派生自Foo?换句话说,我想要函数:

template <typename T> bool is_derived_from(TEBase * p);

这样:

is_derived_from<Foo>(new TEImpl<Foo>) == true
is_derived_from<Foo>(new TEImpl<Bar>) == true

is_derived_from<Foo>(new TEImpl<Unrelated>) == false

特别是,我正在寻找一种通用、非侵入性和高效的解决方案。我找到了解决这个问题的两种方法(下面作为答案发布),但它们都没有解决所有三个标准。

【问题讨论】:

  • 这一切有点傻。您可以只拥有一个std::list&lt;boost::any&gt; 并在any_cast 成功时擦除。无需重新发明几个轮子。
  • @Luc:我对 Boost.TypeErasure 不太熟悉(我会研究一下),但 std::function::target 仅适用于精确类型。在示例中,我要求在所有 Foos 上运行谓词,我无法判断 Bar 是否匹配。
  • @Kerrek:据我所知,any_cast 仅适用于确切类型,因此不适用于继承。
  • @RickYorgason:对,我认为这就是重点?你给DoPred你想要匹配的exact类型...
  • 我现在明白了。这个问题非常嘈杂。你能允许我把它编辑成更简洁的形式吗?

标签: c++ c++11 type-erasure


【解决方案1】:

类似这样的:

template <typename Type, typename UnaryPredicate>
void DoPred(UnaryPredicate pred)
{
    if (T * p = dynamic_cast<Derived<T> *>(this))
    { 
        return pred(p->type);
    }
    return false;
}

这不是 100% 通用的,因为您不能说 DoPred&lt;int&gt;。更通用的解决方案是将virtual std::type_info type() const { return typeid(...); } 成员函数添加到层次结构并使用 that 来确定类型是否匹配(标准类型擦除习语)。不过,这两种方法都使用相同类型的 RTTI。


澄清后:

目前,我认为这无法解决。你所拥有的只是一个TEBase 子对象。它可能是TEImpl&lt;Bar&gt; 的一部分,也可能是TEImpl&lt;Unrelated&gt; 的一部分,但这些类型都与TEImpl&lt;Foo&gt; 无关,这正是您所追求的。

您实际上是在问TEImpl&lt;Bar&gt; 派生自 TEImpl&lt;Foo&gt;。要做到这一点,如果您明白我的意思,您实际上希望 TEImpl&lt;T&gt; 从所有 TEImpl&lt;std::direct_bases&lt;T&gt;::type&gt;...继承。这在 C++11 中是不可能的,但在 TR2 中是可能的。 GCC 已经支持它。这是一个示例实现。 (由于不明确的碱基会导致警告,这可以通过更多的工作来避免,但它仍然有效。)

#include <tr2/type_traits>

struct TEBase { virtual ~TEBase() {} };

template <typename T> struct TEImpl;

template <typename TL> struct Derivator;

template <typename TL, bool EmptyTL>
struct DerivatorImpl;

template <typename TL>
struct DerivatorImpl<TL, true>
: TEBase
{ };

template <typename TL>
struct DerivatorImpl<TL, false>
: TEImpl<typename TL::first::type>
, Derivator<typename TL::rest::type>
{ };

template <typename TL>
struct Derivator
: DerivatorImpl<TL, TL::empty::value>
{ };

template <typename T>
struct TEImpl
: Derivator<typename std::tr2::direct_bases<T>::type>
{
};

template <typename T>
bool is(TEBase const * b)
{
  return nullptr != dynamic_cast<TEImpl<T> const *>(b);
}


struct Foo {};
struct Bar : Foo {};
struct Unrelated {};

#include <iostream>
#include <iomanip>

int main()
{
  TEImpl<int> x;
  TEImpl<Unrelated> y;
  TEImpl<Bar> z;
  TEImpl<Foo> c;

  std::cout << std::boolalpha << "int ?< Foo: " << is<Foo>(&x) << "\n";
  std::cout << std::boolalpha << "Unr ?< Foo: " << is<Foo>(&y) << "\n";
  std::cout << std::boolalpha << "Bar ?< Foo: " << is<Foo>(&z) << "\n";
  std::cout << std::boolalpha << "Foo ?< Foo: " << is<Foo>(&c) << "\n";
}

【讨论】:

  • this,在这种情况下,是一个Base*type,也就是需要传入谓词的,在Derived中。
  • @Luc Danton:感谢您的编辑!因为并发不小心漏掉了 :-S 现在加了。
  • 想到the paper I mentioned earlier 被接受为标准,我真的很兴奋。但看起来 GCC TR2 只是一个孵化器分支,并不能真正代表 C++ 标准化的方向。我错了吗?
  • @RickYorgason:我不了解 TR2 的最新情况。听说已经弃了我也不确定std::bases/std::direct_bases 是否会成为标准,但我希望如此。
【解决方案2】:

我建议阅读文章Generic Programming:Typelists and Applications。 Andrei Alexandrescu 描述了一个临时访问者的实现,它应该可以解决您的问题。另一个很好的资源是他的书Moder C++ Design,其中他以蛮力的方式描述了一个使用相同方法的多调度程序(第 265 页 ...)。

在我看来,这两个资源比可以在这里打印的任何代码都更易于理解。

【讨论】:

  • 啊,输入列表。这仍然意味着在某处明确记录类层次结构,因此它不是灵丹妙药,但我不能声称它不能解决问题中的三个标准。
  • @RickYorgason 类型擦除的问题是您只有基本类型的接口。如果这还不够,您最终会使用其中一个访问者变体,它们是奖牌的另一面:类型恢复。我知道的唯一非侵入性方式是这些临时访问机制并等待......这里是tinyurl.com/n3449 关于类型切换,但我认为这不是一件容易的事情(希望将其作为 GSOC 项目来实现) .
  • 我以前读过那篇论文,我很喜欢这个功能,但我不确定它在这种情况下是否有用。但是,如果我们对语言扩展持开放态度,Type traits and base classes 将完美解决问题。
  • @RickYorgason 现在我真的理解您的担忧(您的问题的所有更新)。是的,n2965 会有所帮助。现在我会让用户通过特征提供列表。我将提供一个默认实现,该实现转发给用户提供的实现(struct trait&lt;T&gt; : trait_impl&lt;T&gt; {}; 用户提供trait_impl)并将默认值更改为可用的编译器实现的特征。
【解决方案3】:

这个解决方案有点滥用异常。如果TEImpl 类型只是抛出它的数据,is_derived_from 可以捕获它正在寻找的类型。

struct TEBase
{
        virtual ~TEBase() {}

        virtual void throw_data() = 0;
};

template <typename T>
struct TEImpl : public TEBase
{
        void throw_data() {
                throw &data;
        }

        T data;
};

template <typename T>
bool is_derived_from(TEBase* p)
{
        try {
                p->throw_data();
        } catch (T*) {
                return true;
        } catch (...) {
                // Do nothing
        }
        return false;
}

此解决方案效果很好。它适用于任何继承结构,并且完全非侵入性。

唯一的问题是它根本没有效率。不打算以这种方式使用异常,我怀疑这种解决方案比其他解决方案慢数千倍。

【讨论】:

  • 它可能根本无法编译。您的 Type 需要默认构造才能被抛出。当您正在编写资源管理器时,我怀疑会是这种情况。
  • 您可以抛出/捕获指向您的类型的指针(例如 throw this)。
  • 好点。我已经更新了答案,改为抛出一个指针,并匹配新的问题文本。
【解决方案4】:

此解决方案涉及比较typeids。 TEImpl 知道自己的类型,因此它可以检查通过的typeid 与自己的类型。

问题是,当你添加继承时,这种技术不起作用,所以我也使用模板元编程来检查类型是否定义了typedef super,在这种情况下它将递归检查其父类。

struct TEBase
{
        virtual ~TEBase() {}

        virtual bool is_type(const type_info& ti) = 0;
};

template <typename T>
struct TEImpl : public TEBase
{
        bool is_type(const type_info& ti) {
                return is_type_impl<T>(ti);
        }

        template <typename Haystack>
        static bool is_type_impl(const type_info& ti) {
                return is_type_super<Haystack>(ti, nullptr);
        }

        template <typename Haystack>
        static bool is_type_super(const type_info& ti, typename Haystack::super*) {
                if(typeid( Haystack ) == ti) return true;
                return is_type_impl<typename Haystack::super>(ti);
        }

        template <typename Haystack>
        static bool is_type_super(const type_info& ti, ...) {
                return typeid(Haystack) == ti;
        }
};

template <typename T>
bool is_derived_from(TEBase* p)
{
        return p->is_type(typeid( T ));
}

为此,Bar 需要重新定义为:

struct Bar : public Foo
{
        typedef Foo super;
};

这应该是相当有效的,但它显然不是非侵入性的,因为无论何时使用继承,它都需要目标类中的typedef supertypedef super 也必须可以公开访问,这违背了许多人认为将您的 typedef super 放在您的私人部分的推荐做法。

它也根本不处理多重继承。

更新:此解决方案可以进一步改进,使其具有通用性和非侵入性。

通用化

typedef super 很棒,因为它是惯用的并且已经在许多类中使用,但它不允许多重继承。为此,我们需要将其替换为可以存储多种类型的类型,例如元组。

如果Bar 被改写为:

struct Bar : public Foo, public Baz
{
        typedef tuple<Foo, Baz> supers;
};

我们可以通过将以下代码添加到 TEImpl 来支持这种形式的声明:

template <typename Haystack>
static bool is_type_impl(const type_info& ti) {
        // Redefined to call is_type_supers instead of is_type_super
        return is_type_supers<Haystack>(ti, nullptr);
}

template <typename Haystack>
static bool is_type_supers(const type_info& ti, typename Haystack::supers*) {
        return IsTypeTuple<typename Haystack::supers, tuple_size<typename Haystack::supers>::value>::match(ti);
}

template <typename Haystack>
static bool is_type_supers(const type_info& ti, ...) {
        return is_type_super<Haystack>(ti, nullptr);
}

template <typename Haystack, size_t N>
struct IsTypeTuple
{
        static bool match(const type_info& ti) {
                if(is_type_impl<typename tuple_element< N-1, Haystack >::type>( ti )) return true;
                return IsTypeTuple<Haystack, N-1>::match(ti);
        }
};

template <typename Haystack>
struct IsTypeTuple<Haystack, 0>
{
        static bool match(const type_info& ti) { return false; }
};

使其非侵入性

现在我们有了一个高效且通用的解决方案,但它仍然具有侵入性,因此它不支持无法修改的类。

为了支持这一点,我们需要一种从类外部声明对象继承的方法。对于Foo,我们可以这样做:

template <>
struct ClassHierarchy<Bar>
{
        typedef tuple<Foo, Baz> supers;
};

为了支持这种风格,首先我们需要非特殊形式的 ClassHierarchy,我们将这样定义:

template <typename T> struct ClassHierarchy { typedef bool undefined; };

我们将使用 undefined 的存在来判断该类是否已被专门化。

现在我们需要向 TEImpl 添加更多功能。我们仍将重用之前的大部分代码,但现在我们还将支持从ClassHierarchy 读取类型数据。

template <typename Haystack>
static bool is_type_impl(const type_info& ti) {
        // Redefined to call is_type_external instead of is_type_supers.
        return is_type_external<Haystack>(ti, nullptr);
}

template <typename Haystack>
static bool is_type_external(const type_info& ti, typename ClassHierarchy<Haystack>::undefined*) {
        return is_type_supers<Haystack>(ti, nullptr);
}

template <typename Haystack>
static bool is_type_external(const type_info& ti, ...) {
        return is_type_supers<ClassHierarchy< Haystack >>(ti, nullptr);
}

template <typename Haystack>
struct ActualType
{
        typedef Haystack type;
};

template <typename Haystack>
struct ActualType<ClassHierarchy< Haystack >>
{
        typedef Haystack type;
};

template <typename Haystack>
static bool is_type_super(const type_info& ti, ...) {
        // Redefined to reference ActualType
        return typeid(typename ActualType<Haystack>::type) == ti;
}

现在我们有了一个高效、通用且非侵入性的解决方案。

未来的解决方案

此解决方案符合标准,但必须明确记录类层次结构仍然有点烦人。编译器已经知道关于类层次结构的所有信息,所以我们不得不做这项繁重的工作是一种耻辱。

此问题的建议解决方案是N2965: Type traits and base classes,即has been implemented in GCC。这篇论文定义了一个direct_bases类,和我们的ClassHierarchy类几乎一模一样,只是它唯一的元素type保证是一个元组,就像supers一样,而且这个类完全由编译器生成.

所以现在我们必须编写一个小样板来让它工作,但如果 N2965 被接受,我们可以摆脱样板并使 TEImpl 更短。

特别感谢 Kerrek SB 和 Jan Herrmann。这个答案从他们的 cmets 中得到了很多灵感。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-12
    相关资源
    最近更新 更多