2012-02-02 50 views
2

我們有一些代碼,看起來像這樣:強制使用固定大小類型的內部API的調用者?

class Serializer 
{ 
public: 
    template<class Type> void Write(const Type& value) 
    { 
     internal_write((byte*)value, sizeof(Type)); 
    } 

    // some overloads of Write that take care of some tricky type we have defined 

private: 
    // implementation of internal_write 
}; 

正如人們所猜測這將數據寫入到磁盤。我們有類似的讀取函數,或多或少將某些字節轉換爲類型。雖然不像它可能的那樣健壯,但它有效,因爲我們在讀取的同一個平臺上編寫 - 這意味着我們寫入的字節與我們需要讀取的字節相匹配。

我們現在正在移動以支持多種平臺。我們在幾個地方的代碼,如:

unsigned long trust_me_this_will_be_fine = get_unsigned_long(); 
a_serializer.Write(trust_me_this_will_be_fine); 

,在當前世界工作正常,但如果我們假設我們要支持的平臺之一具有unsigned long爲32位,並在另一個他們是64位,我們被洗腦了。

我想將Serializer::Write更改爲僅顯式大小的類型作爲參數。我想這個問題:

class Serializer 
{ 
public: 
    void Write(uint32_t value) { ... } 
    void Write(uint64_t value) { ... } 
}; 

但我不覺得這確實解決了這個問題,因爲32位系統上,一個unsigned long會自動轉換成uint32_t但在64位系統上它會自動轉換爲uint64_t

我真的想在這裏是讓Write(uint32_t)接受型uint32_t --meaning,這將需要顯式的轉換參數。我不認爲有一個直接的方法可以做到這一點 - 如果我錯了,請告訴我。

簡而言之,我可以想出兩種方法來解決這個問題。

  1. 申報(但不定義)爲每個類型可以自動轉換成我們支持一種類型的Serializer::Write專用版本。
  2. 不要直接拿uint32_t,而是一個擁有uint32_t的類,並且只有uint32_t的顯式構造函數。

選項2會是這個樣子:

class only_uint32 
{ 
public: 
    uint32_t _value; 
    explicit only_uint32(uint_32 value) : _value(value) { } 
}; 

class Serializer 
{ 
public: 
    void Write(only_uint32 value) { ... } 
}; 

然後調用代碼如下所示:

unsigned long might_just_work = get_unsigned_long(); 
a_serializer.Write(static_cast<uint32_t>(might_just_work)); // should work, and be explicitly sized. 
a_serializer.Write(might_just_work); // won't compile 

我認爲很多人都解決了這類問題。有沒有這樣做的首選方法,我沒有想到?我的想法之一是可怕的,偉大的,可行的嗎?

P.S .:是的,我知道這是一個超長的帖子。不過,這是一個相當複雜和詳細的問題。

更新:

感謝您的想法和幫助。我認爲我們有解決方案,像這樣去:

class Serializer 
{ 
public: 
    template<class Type> void Write32(const Type& value) 
    { 
     static_assert(sizeof(Type) == 4, "Write32 must be called on a 32-bit value."); 
     internal_write(reinterpret_cast<byte*>(value), 4); 
    } 
    // overloads like Write64 and various tricky types as before. 

private: 
    // implementation of internal_write 
}; 

這是一個相當低的成本(以工程時間方面)的解決方案,成功在推你實際上節省了給調用者的知識並強制呼叫者知道他們在呼叫什麼。

+1

如果您有一個類型具有'uint32_t'的顯式構造函數,因爲您可以將其他可轉換爲'uint32_t'的其他元素傳遞給構造函數並且將其轉換爲'uint32_t' – 2012-02-02 05:57:58

+0

感嘆。但當然你是對的。 – SirPentor 2012-02-02 06:17:33

回答

2

你可以做這樣的事情:

template<typename T> 
class MyClass { 
public: 
    static_assert(sizeof(T) == sizeof(uint32_t), "Invalid type"); 
    MyClass(T t) : data(t) { } 

private: 
    uint32_t data; 
}; 

或者,如果你想接受uint32_t S:

template<typename T> 
class MyClass { 
public: 
    static_assert(is_same<T, uint32_t>::value, "Invalid type"); 
    MyClass(T t) : data(t) { } 

private: 
    uint32_t data; 
}; 
2

您可以使用靜態斷言,請參閱http://en.wikipedia.org/wiki/C%2B%2B11#Static_assertions以防止調用者使用「錯誤大小」的類型。這在C++ 11中是可用的,如果你的編譯器不支持它,你可以使用http://www.boost.org/doc/libs/1_48_0/doc/html/boost_staticassert.html,它可以做同樣的事情。

+0

我喜歡這個想法的簡單性。我已經完全遺忘了舊版CASSERT(因爲他們在Windows頭文件中稱它爲「)。 – SirPentor 2012-02-02 06:16:07

+0

其實,等一下。在這種情況下,你會把static_assert放在哪裏?當我們進入Serializer :: Write時,已經太晚了,不是嗎? (儘管@Sethc可能與你描述的是相同的東西......) – SirPentor 2012-02-02 06:32:13

1

您可以使用指針這些內建的:

void Write(const uint32_t* const value); 
void Write(const uint64_t* const value); 

in use:

const uint8_t a= …; 
ser.Write(a); // oops! 
ser.Write(&a); // oops! 

或者你也可以使用不能升級的引用(即如果類型爲內置函數,則傳遞可變引用),但這很糟糕並且有點嘈雜。

此操作,因爲:

  • 的值,常量值,或const參考可以促進或縮小到另一種類型,如果可轉換。
  • 指針,常量指針和非const引用不能被提升。 例外情況:如果您使用的是舊版本的VS,則允許使用價值可變參考促銷(非標準)。
+0

驗證我明白你的意思:這是可行的,因爲編譯器不會自動轉換指針? – SirPentor 2012-02-02 06:17:14

+1

@SirPentor這是正確的 - 答案擴展 – justin 2012-02-02 06:22:31

相關問題