关于《Effective C++》条款27:尽量少做转型动作,派生类转型基类调用的理解
问题
书中有如下代码:
class Window{
public:virtual void onResize(){...}//...
};
class SpecialWindow : public Window{
public:virtual void onResize(){static_casst<Window>(*this).onResize();//...}
};
这个代码的目的是:通过强制类型转换,使派生类直接去调用父类的onResize函数
但是,作者评价道:它看起来对,但实际上错!
我在代码中强调了转型动作。一如你所预取,这段程序将*this转型为Window,对函数onResize的调用也因此调用了Window::onResize。但恐怕你没想到,他调用的并不是当前对象上的函数,而是稍早转型动作所建立的一个“*this对象之base class成分“的暂时副本身上的onResize!(译注:函数就是函数,成员函数只有一份,”调用起哪个对象身上的函数“又有什么关系呢?关键在于成员函数都有个隐藏的this指针,会因此影响成员函数操作的数据。)再说一次,上述代码并非在当前对象上调用Window::onResize之后又在该对象上执行SpecialWindow专属动作。不,它是在”当前对象之base class成分的副本上调用Window::onResize,然后在当前对象身上执行SpecialWindow专属动作。如果Window::onResize修改了对象内容(不能说没有可能性,因为onResize是个non-const成员函数),当前对象其实没被改动,改动的是副本。然而SpecialWindow::onResize内如果也修改对象,当前对象真的会被改动。这使得当前对象进入一种“伤残”状态:其base class成分的更改没有落实,而derived class成分的更改倒是落实了
–摘自原文
现象描述
我们来考虑如下情况:
class Window{
public:virtual void onResize(){b = 10;cout <<"after use static_cast/Base:: b=" << b << endl;}int b=0;
};class SpecialWindow :public Window{
public:virtual void onResize(){static_cast<Window>(*this).onResize();//Window::onResize();}void printB(){cout << b << endl;}
};int main()
{SpecialWindow derive;derive.printB();//修改前derive.onResize();derive.printB();//修改后return 0;
}
如上内容是:构造一个SpecialWindow对象,然后调用该对象的onResize函数(该函数的目的是:对于派生类,它去调用父类的onResize;对于基类,它去修改变量b的值=10)。然后分别打印出调用onResize前后的b参数的值(派生类会继承基类的变量b,这里将其设为了公有,方便在派生类中直接访问)
- 注意这里转型并没有使用指针/引用,所以不存在多态问题
- 我们期望的结果是:当基类修改b的值后,派生类也应当看到该值变成了10
输出结果如下:
//采用static_cast<Window>(*this).onResize();
b:0
after use static_cast/Base:: b=10
b:0//采用Window::onResize();
b:0
after use static_cast/Base:: b=10
b:10
发现当采用
static_cast<Window>(*this).onResize();
时,结果并不是我们所预期的。
其原因正如作者所说:”this对象之base class“成分的暂时副本身上的onResize
。我们将在下文对其进行解释
原因说明
使用static_cast<Window>的时候,发生了拷贝构造,生成了临时对象,而后续在基类对象中做的所有事情,都是对该临时对象做的
//采用static_cast<Window>(*this).onResize();
b:0
this指针:0046FE04
copy construct Window
this指针:0046FC54
after use static_cast/Base:: b=10
b:0//采用Window::onResize();
b:0
this指针:010FF9C4
this指针:010FF9C4
after use static_cast/Base:: b=10
b:10
可以明显的发现:使用转型的时候,this发生了拷贝构造,转而在后续的onResize中,修改的不再是derive对象的内容,而是临时对象的内容。
伤残现象:
如果我们的SpecialWindow::onResize中存在对内容的修改,由于此时的this指针正是我们所构造的变量的this指针,所以它的修改会落实;
而如果Window::onResize中存在对内容的修改,由于此时的this指针指向的是临时对象,所以它的修改并不是我们所期望的对象。所以你就会发现令人疑惑的伤残现象
解决方案
由于在派生类中的同名函数把父类给覆盖了,所以使用Window::onResize指定调用父类的onResize函数。
如此一来,就能避免因使用转型而产生的额外构造开销,以及可能产生的伤残现象。