【问题标题】:C++ Easily Cast Between Templated Pointer TypesC++ 在模板化指针类型之间轻松转换
【发布时间】:2021-01-27 23:02:09
【问题描述】:

假设我有一个模板化的Array<T> 结构,我想从T * 的一种指针类型转换为另一种。具体来说,我有一个返回Array<Entity *> 的函数,我想将其转换为Array<Player *>

Array<Entity *> get_player_entities();

Array<Player *> players = (Array<Player *>)get_player_entities();

请注意,Player 扩展了 Entity。因为指针具有相同的大小,这应该没问题!但我收到以下错误:

error C2440: 'type cast' : cannot convert from 'Array<Entity *>' to 'Array<Player *>'; No constructor could take the source type, or constructor overload resolution was ambiguous

所以我可以做以下,但是有点麻烦:

Array<Entity *> temp = get_player_entities();
Array<Player *> players = *(Array<Player *> *)&temp;

想知道是否有更简单的方法来解决这个问题?为什么不允许这样做?不应该将一种指针类型转换为另一种类型总是有效吗?例如,在非模板世界中,您始终可以在不同的指针类型之间进行转换。

理想情况下,我希望解决方案不涉及额外的模板。例如,我知道我可以将 get_player_entities 方法更改为模板化。

【问题讨论】:

  • 不应该将一种指针类型转换为另一种类型总是有效吗? 除了某些边缘情况(如转换成员函数指针或转换函数指针),通常将一种数据指针类型转换为另一种指针类型要么有效,要么是未定义的行为。第一种情况,强制转换不是强制转换指针类型,所以这个问题无关紧要。第二种情况,它是未定义的行为,所以任何事情都可能发生。
  • 你这个有点麻烦的解决方案是可怕的。如果您要点亮一个包含转换后的指针的新容器,请使用 std::transform 或类似名称。无论如何,您都会受到制作容器副本的影响。关于您的问题,“不应该将一种指针类型转换为另一种类型总是有效”不,它不应该,而且它没有。
  • 这与大小无关;这是关于语义的。指向基址的指针与指向派生的指针具有不同的操作集。在某些情况下,其中一种类型的对象可以转换为另一种,但没有大锤总是强制转换。
  • 由于Array 是您自己的类型,您可以考虑添加一个函数来进行转换:template &lt;class T&gt; T* to_type(int idx) { return (T*)(*this)[index]); }。您可以使用显式类型调用它:my_array.to_type&lt;Player&gt;(3);

标签: c++ arrays templates


【解决方案1】:

因此,一般来说,向下转换是不安全的。您可以拥有同时扩展 EntityFoo,并且将所有 Entity 强制转换为 Player 而不进行检查会导致错误。

有一些方法可以做你想做的事(例如,为Array&lt;T&gt; 提供一个显式构造函数),但是因为你正在处理指针,我不建议这样做:你正在处理原始指针,看起来,所以处理所有权和范围会很棘手。如果没有看到 Array&lt;T&gt; 是什么,真的很难说这是多么安全或危险,最后,看起来你正在重新实现 STL 的一部分。

如果您知道 ArrayEntity*> 确实是玩家列表,则使用 Player 作为类型,而不是 Entity。如果没有,并且您想获取所有玩家的列表,那么简单的方法是迭代列表中的所有实体对象,然后在每个指针上调用dynamic_cast&lt;Player*&gt;,将其放入新的Array这通常是一种糟糕的代码气味并且非常昂贵。这意味着您已经破坏了封装 - 您应该重新考虑程序中类型和/或容器的层次结构。

【讨论】:

    【解决方案2】:

    想知道是否有更简单的方法来解决这个问题?为什么不允许这样做?

    Array&lt;Base&gt; 不能转换为Array&lt;Derived&gt; 的一般原因是实例化模板会创建一个 new 类型。同一模板的两个实例化是两个独立的类型。当我们说Array&lt;Base&gt;(或其他)时,我们不再谈论称为Array 的模板,而是谈论称为Array&lt;Base&gt; 的具体类型。

    即使模板没有专门化,许多模板也非常依赖于它们的类型参数,以至于它们会根据所使用的类型参数而发生显着变化。

    不应该将一种指针类型转换为另一种指针类型总是有效吗?例如,在非模板化的世界中,您始终可以在不同的指针类型之间进行转换。

    当然,Entity*Player* 之间的转换可以正常工作。不过,这根本不是你想要做的——你试图在独立类型 Array&lt;Entity*&gt;Array&lt;Player*&gt; 之间进行转换。

    有一种方法可以做你想做的事,但它是编写一个包装器类型来为你做指针转换。不要强制转换顶级数组对象并强制复制。

    例如,

    template <class Derived>
    class ArrayView
    {
        Array<Entity*> &data_;
    
    public:
        explicit ArrayView(Array<Entity*> &origin) : data_(origin) {}
    
        Derived* front() { return static_cast<Derived*>(data_.front()); }
    };
    

    最后,你会得到类似的代码

    ArrayView<Player> players{entities};
    

    或其他。请注意,您需要注意原始数组的生命周期,并且需要使用强制转换等复制整个接口。 哦,你可能想使用std::is_base_ofstd::is_convertible 或其他东西来确保在出错时得到有用的错误。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-06-16
      • 1970-01-01
      • 2015-03-16
      • 2019-12-09
      • 1970-01-01
      • 1970-01-01
      • 2021-09-20
      相关资源
      最近更新 更多