2013年8月22日 星期四

有繼承的情況下, C++ method 存取到誰的 member field?

Effective C++ item 27 "盡量少做轉型" 提到下列轉型看起來像對的, 實際是錯的:

class SpecialWindow : public Window {
public:
  virtual void onReSize() {
    static_cast<Window>(*this).onResize();
    ...
  }
}

對這點感到很納悶, 於是寫個小程式實驗看看。結果發覺自己沒看清楚, 書上舉例是轉型 Window, 不是轉型 Window*, 所以 Window::onResize() 作用到新產生的物件身上, 結果不同於呼叫 Window::onResize()。

既然已經寫了小程式做實驗, 順便記在這裡供日後備忘。

要點如下:

  • 類別 C 的 method 會存取自己的 member field x, 若 C 本身沒有這個 member field, 會往父類別 A 找。
  • 承上, A 和 C 的 method 存取到的 x 是同一個 x, 也就是 A::x。
  • 若類別 B 宣告和父類別 A 同名稱的 member field x, 則類別 B 和父類別 A 各自擁有一份不同位置的 member field x。
  • 承上, A 的 method 會存取 A::x; B 的 method 存取 B::x。呼叫到誰的 method, 就知道改到誰的 x。

題外話, 允許子類別覆寫或重覆宣告同名 method 或 member field 是個糟糕的主意, 再加上可以轉型, 而且轉型後結果還會不一樣, 真是火上加油啊.......。了解 C++ 愈多, 愈覺得要從實作層面才能理解它的語法。

程式碼

#include <stdio.h>
class B;
class A
{
public:
A() : x(0) {}
A(const B& b) { printf("copy constructor A is called\n"); }
void set_x(int x) { this->x = x * 10; }
int x;
};
class A;
class B : public A
{
public:
B() : x(0) {}
void set_x(int x)
{
this->x = x;
printf("%d %d\n", x, A::x);
static_cast<A>(*this).set_x(x);
printf("%d %d\n", x, A::x);
static_cast<A*>(this)->set_x(x);
printf("%d %d\n", x, A::x);
A::set_x(x);
printf("%d %d\n", x, A::x);
}
int x;
};
class C : public A
{
public:
void set_x(int x)
{
this->x = x;
printf("%d %d\n", x, A::x);
static_cast<A>(*this).set_x(x);
printf("%d %d\n", x, A::x);
static_cast<A*>(this)->set_x(x);
printf("%d %d\n", x, A::x);
A::set_x(x);
printf("%d %d\n", x, A::x);
}
// Does not have "C.x".
};
int main(void)
{
B b;
printf("\nb.set_x(3)\n");
b.set_x(3);
C c;
printf("\nc.set_x(3)\n");
c.set_x(3);
printf("\nmethod address\n");
printf("&A::set_x %p\n", &A::set_x);
printf("&B::set_x %p\n", &B::set_x);
printf("&C::set_x %p\n", &C::set_x);
printf("\nmember field address\n");
printf("&b.x %p\n", &b.x);
printf("&((A*)&b)->x %p\n", &((A*)&b)->x);
printf("&c.x %p\n", &c.x);
printf("&((A*)&c)->x %p\n", &((A*)&c)->x);
return 0;
}
view raw a.cpp hosted with ❤ by GitHub

範例輸出

$ ./a

b.set_x(3)
3 0
copy constructor A is called
3 0
3 30
3 30

c.set_x(3)
3 3
3 3
3 30
3 30

method address
&A::set_x     0x4006c8
&B::set_x     0x400710
&C::set_x     0x4007da

member field address
&b.x          0x7fffb4ef43a4
&((A*)&b)->x  0x7fffb4ef43a0
&c.x          0x7fffb4ef43b0
&((A*)&c)->x  0x7fffb4ef43b0

沒有留言:

張貼留言

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...