【问题标题】:C++ cast to array of a smaller sizeC ++强制转换为较小大小的数组
【发布时间】:2011-03-10 07:43:14
【问题描述】:

这是一个关于 C++ 语言的各种怪癖的有趣问题。我有一对函数,它们应该用矩形的角填充点数组。它有两个重载:一个采用Point[5],另一个采用Point[4]。 5 点版本指的是一个封闭的多边形,而 4 点版本是指您只需要 4 个角,句号。

显然这里有一些重复的工作,所以我希望能够使用 4 点版本来填充 5 点版本的前 4 点,所以我没有重复该代码。 (并不是说复制太多,但每当我复制和粘贴代码时,我都会有可怕的过敏反应,我想避免这种情况。)

问题是,C++ 似乎并不关心将T[m] 转换为T[n] 的想法,其中n < mstatic_cast 似乎认为这些类型由于某种原因不兼容。 reinterpret_cast 处理得很好,当然,但它是一种危险的动物,作为一般规则,最好尽可能避免。

所以我的问题是:有没有一种类型安全的方法可以将一个大小的数组转换为数组类型相同的较小大小的数组?

[编辑] 代码,是的。我应该提到,参数实际上是对数组的引用,而不是简单的指针,所以编译器知道类型差异。

void RectToPointArray(const degRect& rect, degPoint(&points)[4])
{
    points[0].lat = rect.nw.lat; points[0].lon = rect.nw.lon;
    points[1].lat = rect.nw.lat; points[1].lon = rect.se.lon;
    points[2].lat = rect.se.lat; points[2].lon = rect.se.lon;
    points[3].lat = rect.se.lat; points[3].lon = rect.nw.lon;
}
void RectToPointArray(const degRect& rect, degPoint(&points)[5])
{
    // I would like to use a more type-safe check here if possible:
    RectToPointArray(rect, reinterpret_cast<degPoint(&)[4]> (points));
    points[4].lat = rect.nw.lat; points[4].lon = rect.nw.lon;
}

[Edit2] 通过引用传递数组的目的是让我们至少可以模糊地确定调用者正在传递正确的“输出参数”。

【问题讨论】:

  • 请发布代码,特别是您的函数声明。声明为将参数作为数组的函数实际上将参数作为指针,因此您不能因参数的数组大小差异而重载。
  • 我应该提到它们实际上是数组引用,因此编译器会保持数组大小的意识。

标签: c++ arrays reinterpret-cast


【解决方案1】:

所以我的问题是:有没有 类型安全的转换数组的方法 一个尺寸到一个较小尺寸的数组 数组类型哪里一样?

没有。我认为该语言根本不允许您这样做:考虑将 int[10] 转换为 int[5]。但是,您始终可以获得指向它的指针,但我们不能“欺骗”编译器认为固定大小具有不同数量的维度。

如果您不打算使用 std::vector 或其他容器,它们可以在运行时正确识别内部点的数量,并在一个函数中方便地完成这一切,而不是根据数量调用两个函数重载元素,而不是试图做疯狂的演员,至少认为这是一种改进:

void RectToPointArray(const degRect& rect, degPoint* points, unsigned int size);

如果您准备使用数组,您仍然可以像这样定义一个泛型函数:

template <class T, size_t N>
std::size_t array_size(const T(&/*array*/)[N])
{
    return N;
}

... 并在调用 RectToPointArray 时使用它来传递“大小”的参数。然后你有一个可以在运行时确定的大小,并且使用 size - 1 很容易,或者更适合这种情况,只需输入一个简单的 if 语句来检查是否有 5 个或 4 个元素。

稍后,如果您改变主意并使用 std::vector、Boost.Array 等,您仍然可以使用相同的旧函数而无需修改它。它只要求数据是连续的和可变的。您可以对此有所了解并应用非常通用的解决方案,例如,只需要前向迭代器。然而,我认为这个问题并不复杂到需要这样的解决方案:就像用大炮杀死苍蝇一样;苍蝇拍没问题。

如果您真的确定了现有的解决方案,那么这样做很容易:

template <size_t N>
void RectToPointArray(const degRect& rect, degPoint(&points)[N])
{
    assert(N >= 4 && "points requires at least 4 elements!");
    points[0].lat = rect.nw.lat; points[0].lon = rect.nw.lon;
    points[1].lat = rect.nw.lat; points[1].lon = rect.se.lon;
    points[2].lat = rect.se.lat; points[2].lon = rect.se.lon;
    points[3].lat = rect.se.lat; points[3].lon = rect.nw.lon;

    if (N >= 5)
        points[4].lat = rect.nw.lat; points[4].lon = rect.nw.lon;
}

是的,有一项不必要的运行时检查,但尝试在编译时进行检查可能类似于从手套箱中取出东西以试图提高汽车的燃油效率。由于 N 是编译时常量表达式,编译器很可能会在 N

【讨论】:

    【解决方案2】:

    我不认为你对问题的构想/思考是正确的。您通常不需要具体键入具有 4 个顶点的对象与具有 5 个顶点的对象。

    但如果您必须键入它,那么您可以使用structs 来具体定义类型。

    struct Coord
    {
        float lat, long ;
    } ;
    

    然后

    struct Rectangle
    {
        Coord points[ 4 ] ;
    } ;
    
    struct Pentagon
    {
        Coord points[ 5 ] ;
    } ;
    

    那么,

    // 4 pt version
    void RectToPointArray(const degRect& rect, const Rectangle& rectangle ) ;
    
    // 5 pt version
    void RectToPointArray(const degRect& rect, const Pentagon& pent ) ;
    

    我认为这个解决方案有点极端,但是 std::vector&lt;Coord&gt; 可以按照asserts 的预期检查其大小(为 4 或 5),就可以了。

    【讨论】:

    • 这不是一个真正的选择。如果它是一个新项目,我会同意你的看法,但这是一个非常古老的程序,而且这种重构级别目前不在路线图上。 (还是)感谢你的建议。 :)
    【解决方案3】:

    我猜你可以使用函数模板特化,像这样(简化示例,第一个参数被忽略,函数名被 f() 替换,等等):

    #include <iostream>
    using namespace std;
    
    class X
    {
    };
    
    template<int sz, int n>
    int f(X (&x)[sz])
    {
        cout<<"process "<<n<<" entries in a "<<sz<<"-dimensional array"<<endl;
        int partial_result=f<sz,n-1>(x);
        cout<<"process last entry..."<<endl;
    
        return n;
    }
    //template specialization for sz=5 and n=4 (number of entries to process)
    template<>
    int f<5,4>(X (&x)[5])
    {
        cout<<"process only the first "<<4<<" entries here..."<<endl;
    
        return 4;
    }
    
    
    int main(void)
    {
        X u[5];
    
        int res=f<5,5>(u);
        return 0;
    }
    

    当然,您必须处理其他(潜在危险)特殊情况,例如 n={0,1,2,3},您最好使用 unsigned int 而不是整数。

    【讨论】:

      【解决方案4】:

      我认为通过重载来做到这一点不是一个好主意。函数的名称不会告诉调用者它是否要填充一个打开的数组。如果调用者只有一个指针并且想要填充坐标(假设他想要填充多个矩形以成为更大数组中不同偏移量的一部分)怎么办?

      我会通过两个函数来做到这一点,并让它们获取指针。大小不是指针类型的一部分

      void fillOpenRect(degRect const& rect, degPoint *p) { 
        ... 
      }
      
      void fillClosedRect(degRect const& rect, degPoint *p) { 
        fillOpenRect(rect, p); p[4] = p[0]; 
      }
      

      我看不出这有什么问题。您的重新解释演员在实践中应该可以正常工作(我看不出会出现什么问题 - 对齐和表示都是正确的,所以我认为仅仅形式上的不确定性不会在这里实现),但正如我所说以上我认为没有充分的理由让这些函数通过引用来获取数组。


      如果你想通用的,你可以通过输出迭代器来写

      template<typename OutputIterator> 
      OutputIterator fillOpenRect(degRect const& rect, OutputIterator out) { 
        typedef typename iterator_traits<OutputIterator>::value_type value_type;
        value_type pt[] = { 
          { rect.nw.lat, rect.nw.lon },
          { rect.nw.lat, rect.se.lon },
          { rect.se.lat, rect.se.lon },
          { rect.se.lat, rect.nw.lon }
        };
        for(int i = 0; i < 4; i++)
          *out++ = pt[i];
        return out;
      }
      
      template<typename OutputIterator>
      OutputIterator fillClosedRect(degRect const& rect, OutputIterator out) { 
        typedef typename iterator_traits<OutputIterator>::value_type value_type;
        out = fillOpenRect(rect, out); 
      
        value_type p1 = { rect.nw.lat, rect.nw.lon };
        *out++ = p1;
        return out;
      }
      

      然后您可以将它与向量和数组一起使用,无论您最喜欢什么。

      std::vector<degPoint> points;
      fillClosedRect(someRect, std::back_inserter(points));
      
      degPoint points[5];
      fillClosedRect(someRect, points);
      

      如果你想编写更安全的代码,你可以使用带有反向插入器的向量方式,如果你使用较低级别的代码,你可以使用指针作为输出迭代器。

      【讨论】:

      • 通过传递对数组的引用,编译器将执行类型检查并验证数组大小是否正确,如果错误地在 4 点的矩形上调用 fillClosedRect,则会出现编译时错误。是否可以将一个大小的数组静态转换为不同大小的数组?
      • @Stephen 不可能静态转换这些东西。但是编译器检查它的意义是没有实际意义的。您只能传递该特定大小的数组,即使大小已由编译器验证 - 仅涵盖类型检查。它不能确保引用将引用正确的数组对象。如果调用者在某个地方搞砸了,悬空引用仍然是可能的。我完全支持编译时检查,但在这种情况下似乎没有任何优势。
      【解决方案5】:

      你为什么不只传递一个标准指针,而不是像这样的大小指针

      void RectToPointArray(const degRect& rect, degPoint * points ) ;
      

      【讨论】:

      • 这是有争议的。此功能用于替换手动完成此转换的许多情况,并且在每种情况下,无一例外,点集都保存在堆栈数组中,该数组在范围结束时被丢弃,因此知道大小不是问题(而且堆数组上的这个操作对应用程序没有任何意义)。一般来说,这里的目标是抛出编译器错误,而不是访问违规,然后必须通过大量代码库进行追溯。
      【解决方案6】:

      我会使用 std::vector(这真的很糟糕,不应该使用) 在某些极端情况下,您甚至可以通过指针使用普通数组,例如 Point*,然后您不应该有这样的“选角”烦恼。

      【讨论】:

        猜你喜欢
        • 2022-01-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-06-18
        • 1970-01-01
        • 1970-01-01
        • 2019-05-27
        • 1970-01-01
        相关资源
        最近更新 更多