2010-06-30 92 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是一個編譯時常量表達式,因此編譯器很可能會認識到條件始終爲假,並且只消除整段代碼。