9

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

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

问题是,C++ 似乎并不关心将 a 转换T[m]T[n]where的想法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] 通过引用传递数组的目的是让我们至少可以模糊地确定调用者正在传递正确的“输出参数”。

4

6 回答 6

5

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

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

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

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

于 2010-06-30T19:28:21.837 回答
3

我会使用std::vectoror (这真的很糟糕,不应该使用)在某些极端情况下,你甚至可以通过指针使用普通数组Point*,然后你不应该有这样的“铸造”麻烦。

于 2010-06-30T19:36:32.130 回答
1

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

void RectToPointArray(const degRect& rect, degPoint * points ) ;
于 2010-06-30T19:36:44.910 回答
1

我不认为你对问题的框架/思考是正确的。您通常不需要具体键入具有 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<Coord>您可以按照 s 的预期检查它的大小(为 4 或 5)assert,就可以了。

于 2010-06-30T21:31:32.140 回答
0

我猜你可以使用函数模板特化,像这样(简化的例子,第一个参数被忽略,函数名被 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},并且您最好使用无符号整数而不是整数。

于 2010-06-30T21:14:28.860 回答
0

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

不,我认为该语言根本不允许您这样做:考虑将 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 < 5 时识别出条件始终为假,并删除整个代码部分。

于 2010-06-30T23:55:26.900 回答