2010-06-30 46 views
7

下面是关于C++语言各种怪癖的一个有趣问题。我有一对函数,它们应该用一个矩形的角来填充一个点的数组。有两个重载:一个需要Point[5],另一个需要Point[4]。 5点的版本是指一个封闭的多边形,而4点的版本是当你只需要4个角落时。将C++转换为更小的数组

显然这里有一些重复的工作,所以我希望能够使用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]传递一个数组按引用的点是,使我们可以为至少隐约确保呼叫者被以正确的传递“输出参数”。

+0

请张贴代码,特别是您的函数声明。声明将参数作为数组的参数实际上将参数指定为指针,因此不能因参数的数组大小差异而导致重载。 – 2010-06-30 19:27:56

+0

我应该提到它们实际上是按引用排列的,因此编译器会保持对数组大小的认识。 – 2010-06-30 19:35:25

回答

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

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

+0

通过传递对数组的引用,编译器将执行类型检查并验证数组大小是否正确,如果错误地fillClosedRect在4个点上被调用,则会给出编译时错误。 是否有可能static_cast一个大小的数组到一个不同大小的数组? – 2010-06-30 20:20:14

+0

@Stephen这是不可能的静态投这样的事情。但编译器检查它的意义不大。您只能传递特定大小的数组,并且即使大小已由编译器验证 - 只涵盖类型检查。它不确保引用将引用一个正确的数组对象。如果主叫方在某处发生混乱,悬挂引用仍然可能。我都是为编译时检查,但在这种情况下,它似乎没有优势。 – 2010-06-30 20:23:32

3

我会用在某些极端情况下std::vector(这是非常糟糕的,不应该被使用)你甚至可以通过指针使用普通阵列像Point*,然后你不应该有这样的“铸造”烦恼。

1

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

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

缺乏类型安全。 – 2010-06-30 20:21:12

+0

这似乎有点极端, – bobobobo 2010-06-30 21:21:19

+0

这是有争议的。这个函数是用来替换手动完成这个转换的一些情况,并且在任何情况下都毫无例外地将这些点集合保存在一个堆栈数组中,这个数组会在作用域的末尾被丢弃,所以知道这个大小并不是问题(并且对堆数组的这种操作对于应用程序来说没有任何意义)。通常,这里的目标是抛出编译器错误,而不是访问冲突,然后通过大量代码库进行追溯。 – 2010-07-01 12:42:31

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}而你可能使用unsigned int的i更好而不是整数。

1

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

但是,如果您必须键入它,则可以使用struct来具体定义类型。

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>(是4或5)正如预期的那样,assert s,会做得很好。

+0

这不是一个真正的选择。如果这是一个新项目,我会同意你的看法,但这是一个非常老的程序,目前这个重构级别并不在路线图上。 (还是)感谢你的建议。 :) – 2010-07-01 12:34:56

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'参数。然后,您可以在运行时确定大小,并且可以轻松处理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是一个编译时常量表达式,因此编译器很可能会认识到条件始终为假,并且只消除整段代码。