今天踩到一個和 C++ 指標轉型相關的 bug, 幸好以前看《深度探索 C++ 物件模型》時有個模糊的印象, 知道物件指標轉型後, 位置可能會變, 發現這問題後很快就想通了。
先寫個小程式實驗一下:
#include <iostream> struct A { int x; }; struct B { int y; }; struct C : public A, B { int z; }; int main() { C *c = new C(); A *a = c; B *b = c; void* v = c; std::cout << a << " " << b << " " << c << " " << v << std::endl; return 0; }
範例輸出:
0xbd0010 0xbd0014 0xbd0010 0xbd0010
由此可得知 a、c、v 位置一樣, 但 b 不同。
若用指標相等做邏輯判斷, 要留意轉型帶來的影響, 特別是中間有轉型成 void* 的時候, 可能傳了同樣的物件, 卻因型別不同造成誤判。
以下是一個示意的例子:
class Foo final { public: void Save(const Data& data, void* source); ... private: ... FooCallback *m_callback; } void Foo::Save(const Data& data, void* source) { ... if (m_callback && source != m_callback) { m_callback->OnSave(data); } }
有不同的來源會呼叫 Foo::Save, 此時 Foo 會通知事先註冊的 callback, 但要避開 callback object 主動呼叫 Foo::Save 的情況。
乍看之下有檢查 source != m_callback 即可安心。但若 callback object (即有繼承 FooCallback 的類別的物件) 呼叫 Foo::Save 時這麼寫:
Foo *foo = new Foo(); ... foo->Save(data, this);
有可能因指標型別不同, 而得到不同的位置, 導致 source != m_callback 為真。
反之, 這樣的寫法才能安全過關:
foo->Save(data, (FooCallback*)this);
C++ 的世界真是處處驚奇啊!
struct C 同時有 A, B 兩個部份, 而它的 B 部份又剛好放在 A 後面, 如此而已 ...
回覆刪除ref.《深度探索C++ 物件模型》
《深度探索C++ 物件模型》真是好書, 就是有看過它, 遇到的時候才沒有大叫「搞什麼鬼~~~」XD
回覆刪除