简单背景描述

这个问题是我在阅读Effective C++条例27中所涉及到一个小知识点,其主要是在关注当我们需要在派生类的虚函数中调用对应的父类虚函数时,我们的一些不恰当转型(cast)操作可能会让我们的当前派生类对象进入一种”伤残状态”,即派生类的基类数据部分没有如我们所想的发生改变,而是只改变了其派生类的成员数据。当然这么说有点过于抽象,我还是在下面贴出实际代码来解释一下吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <format>
using namespace std;

// 基类定义
class Base{
private:
int m_base;
public:
Base(int base) :m_base(base) {}
virtual void Resize() {
m_base = -1;
}
int GetBase() {
return m_base;
}
};

// 派生类的定义
class Derived : public Base {
private:
int m_derived;
public:
Derived(int base, int derived): Base(base), m_derived(derived) {}
virtual void Resize() override {
// Base::resize();
static_cast<Base>(*this).Resize();
m_derived = -1;
}
void Print() {
cout << format("Base part is {}, Derived part is {}!", GetBase(), m_derived); //c++20新特性
}
};

int main() {
Derived d(0, 0);
d.Resize();
d.Print(); // 实际上会输出 Base part is 0, Derived part is -1! 这并不是我们想要的!
return 0;
}

可以看到上述代码里,我们在Derived类中的resize方法里调用了Base类的对应方法,我们希望通过这样操作来同时改变一个派生类对象的基类成员数据和派生类成员数据,即我们希望通过上述代码的操作来把m_base和m_derived都置为-1。

但是遗憾的是,上述代码虽然看起来很有道理,我们将*this通过static_cast转为基类对象,并调用基类的resize函数来改变对应的基类成员数据,但是实际上编译器会拷贝一个*this的副本,并且在副本上进行操作,这也就导致实际上我们实际上改变的是一个拷贝的Derived对象的基类成员,而不是改变的实际的Derived对象。所以上述代码的输出是Base part is 0, Derived part is -1! 只有将上述代码中的cast操作换为注释掉的Base::resize(),我们才能得到想要的输出,也即是Base part is -1, Derived part is -1!

PS: 不过这种情况下其实是不是把基类的成员定义为protected更合理些, 毕竟这个关键字就是为了让子类访问父类成员变量而设计的。