2010-07-01 60 views
6

我沒有發現任何直接相關的搜索,所以請原諒,如果這是重複的。將任何數據類型序列化爲矢量<uint8_t> - 使用reinterpret_cast?

我正在做的是通過網絡連接序列化數據。我的方法是將我需要轉換的所有內容轉換爲std::vector<uint8_t>,並在接收端將數據解包到適當的變量中。我的方法是這樣的:

template <typename T> 
inline void pack (std::vector<uint8_t>& dst, T& data) { 
    uint8_t * src = static_cast < uint8_t* >(static_cast < void * >(&data)); 
    dst.insert (dst.end(), src, src + sizeof (T)); 
} 

template <typename T> 
inline void unpack (vector <uint8_t >& src, int index, T& data) { 
    copy (&src[index], &src[index + sizeof (T)], &data); 
} 

我敢使用像

vector<uint8_t> buffer; 
uint32_t foo = 103, bar = 443; 
pack (buff, foo); 
pack (buff, bar); 

// And on the receive side 
uint32_t a = 0, b = 0; 
size_t offset = 0; 
unpack (buffer, offset, a); 
offset += sizeof (a); 
unpack (buffer, offset, b); 

我關心的是

uint8_t * src = static_cast < uint8_t* >(static_cast < void * >(&data));

線(我明白做同樣的reinterpret_cast )。有沒有更好的方法來實現這個沒有雙重演員?

我天真的做法是隻使用static_cast< uint8_t* >(&data)失敗。我been told in the pastreinterpret_cast是壞的。所以如果可能的話,我想避免它(或者我目前的構造)。

當然,總有uint8_t * src = (uint8_t *)(&data)

對此提出建議?

回答

16

我的建議是忽略所有的人告訴你reinterpret_cast是壞的。他們告訴你這是不好的,因爲採用一種類型的內存映射並假裝它是另一種類型通常不是一個好習慣。但在這種情況下,這正是你想要做的,因爲你的整個目的是將內存映射作爲一系列字節傳輸。

它比使用雙重static_cast好得多,因爲它完全詳細說明了您正在採取一種類型並故意假裝它是別的事實。這種情況正是reinterpret_cast的目的,並且將它與一個void指針中介結合使用僅僅是掩蓋了你的意思而沒有任何好處。

而且,我敢肯定,你意識到這一點,但在T.

1

你沒有做任何實際編碼在這裏觀看的指針,你只是複製的原始表示將內存中的數據轉換爲字節數組,然後通過網絡發送出去。這是行不通的。這裏有一個簡單的例子,爲什麼:當你用你的方法來發送一個B出在網絡

struct A { 
    int a; 
}; 

struct B { 
    A* p_a; 
} 

會發生什麼?收件人收到p_a,您的計算機上某個對象A的地址,但該對象不在其計算機上。即使您也向他們發送了A對象,它將不在同一個地址。如果您只發送原始B結構,則無法工作。這甚至不考慮更多的細微問題,如字節序和浮點表示,它們可能會影響intdouble等簡單類型的傳輸。

你現在正在做的事情基本上與將其投入到uint8_t*就其是否會工作而言根本沒有什麼不同(除了最微不足道的情況之外,這是行不通的)。

你需要做的是設計一個方法系列化。序列化意味着解決這類問題的任何方式:如何將內存中的對象以一種可以在另一側進行有意義重構的形式出現在網絡上。這是一個棘手的問題,但它是一個衆所周知的並且一再解決的問題。這是一個很好的閱讀起點:http://www.parashift.com/c++-faq-lite/serialization.html

+0

所以,是的,用詞不當。關於你的評論的其餘部分:正如所提出的,這個問題是詢問是否需要'reinterpret_cast'(或類似)的簡化 - 我將重新命名爲更具體。我意識到傳輸數據的細微之處,並且內部所有東西都有一個打包/解壓縮,基本上按照上面描述的爲自己的數據進行。 – ezpz 2010-07-01 20:48:48

2

通過利用任何指針可隱式轉換爲void*的事實,您可以擺脫一次演員陣容。此外,您可能要添加一些const

//Beware, brain-compiled code ahead! 
template <typename T> 
inline void encode (std::vector<uint8_t>& dst, const T& data) 
{ 
    const void* pdata = &data; 
    uint8_t* src = static_cast<uint8_t*>(pdata); 
    dst.insert(dst.end(), src, src + sizeof(T)); 
} 

你可能要添加一個編譯時檢查T是一個POD,沒有struct,並沒有指針。

但是,在字節級別解釋某個對象的內存永遠不會是保存週期。如果你必須這樣做,那麼用一個很好的包裝來做(如你所做的那樣),然後克服它。當你移植到不同的平臺/編譯器時,請注意這些事情。

+0

我在那裏有'const',但是爲了簡潔而省略。但是,我沒有檢查指針和/或結構。這僅供我自己使用,但最好添加這些檢查以確保安全。謝謝。 – ezpz 2010-07-01 20:53:56

6

你的情況正是reinterpret_cast的目的,它比雙重static_cast更簡單,並清楚地記錄你在做什麼。

爲安全起見,你應該使用unsigned char而不是uint8_t

  • reinterpret_castunsigned char *,然後提領該結果指針是安全和便攜和[basic.lval]是明確允許§3.10/10
  • reinterpret_caststd::uint8_t *,然後解除引用所得指針是違反嚴格別名規則的和是如果std::uint8_t作爲EXT被實現未定義的行爲結束的無符號整數類型。

    如果存在,uint8_t必須始終具有與unsigned char相同的寬度。但是,它不需要是相同的類型;它可能是一個不同的擴展整數類型。它也不必具有與unsigned char相同的表示(參見When is uint8_t ≠ unsigned char?)。

    (這不是完全的假設:製作[u]int8_t一種特殊的擴展整型允許一些積極的優化)

如果你真的想uint8_t,你可以添加一個:

static_assert(std::is_same<std::uint8_t, unsigned char>::value, 
       "We require std::uint8_t to be implemented as unsigned char"); 

所以該代碼不會在其導致未定義行爲的平臺上編譯。

+0

+1因爲這比鏈接的'static_cast's更好,特別是關於'uint8_t'的警告。我過去讀過這樣的一篇文章,甚至可能是同一篇文章 - 並且很快就要做很多's/uint8_t/unsigned char/g';) – 2016-04-12 10:16:46

相關問題