Ano*_*ken 5 c++ destructor multiple-inheritance this-pointer
我对基类析构函数中的 this 指针有一个奇怪的问题。
问题描述:
我有 3 个班级:A1,A2,A3
A2从A1公开继承,从A3私下继承
class A2:private A3, public A1 {...}
Run Code Online (Sandbox Code Playgroud)
A3有一个函数getPrimaryInstance() ...返回A1类型对A2实例的引用:
A1& A3::getPrimaryInstance()const{
static A2 primary;
return primary;
}
Run Code Online (Sandbox Code Playgroud)
而A3的构造是这样的:
A3(){
getPrimaryInstance().regInst(this);
}
Run Code Online (Sandbox Code Playgroud)
(其中regInst(...)是A1中定义的函数,用于存储指向所有A3实例的指针)
类似的A3析构函数:
~A3(){
getPrimaryInstance().unregInst(this);
}
Run Code Online (Sandbox Code Playgroud)
^这就是问题发生的地方!
当名为primary的静态A2实例在程序终止时被销毁时,将调用A3析构函数,但在~A3 中,我尝试访问我正在销毁的同一实例上的函数。 =>运行时访问冲突!
所以我认为可以用一个简单的 if 语句来修复,如下所示:
~A3(){
if(this != (A3*)(A2*)&getPrimaryInstance()) //Original verison
//if (this != dynamic_cast<A3*>(static_cast<A2*>(&getPrimaryInstance())))
//Not working. Problem with seeing definitions, see comments below this post
getPrimaryInstance().unregInst(this);
}
Run Code Online (Sandbox Code Playgroud)
(双重转换的原因是继承:)
A1 A3
。\ /
。A2
(但这并不重要,可以只是(int) -casted 或其他)
关键是它仍然崩溃。使用调试器单步执行代码显示,当我的A2 主实例被破坏时,析构函数中的this指针和我通过调用getPrimaryInstance()获得的地址由于某种原因根本不匹配!我不明白为什么this指针指向的地址总是与它(以我有限的知识)应该的不同。:(
在析构函数中这样做:
int test = (int)this - (int)&getPrimaryInstance();
Run Code Online (Sandbox Code Playgroud)
还向我展示了差异不是恒定的(我简要地有一个理论,即存在一些恒定的偏移量)所以它就像是两个完全不同的对象,而它应该是同一个。:(
我在 VC++ Express (2008) 中编码。在谷歌搜索后,我发现了以下 MS 文章:
FIX: The "this" Pointer Is Incorrect in Destructor of Base Class
这与我遇到的问题不同(并且据说在 C++.Net 2003 中也已修复)。但不管症状似乎很相似,他们确实提出了一个简单的解决方法,所以我决定尝试一下:
删除 not-working- if -语句并在第二次继承前添加virtual到A2,如下所示:
class A2:private A3, public A1 {...} // <-- old version
class A2:private A3, virtual public A1 {...} //new, with virtual!
Run Code Online (Sandbox Code Playgroud)
它奏效了!在此终场仍是看似错误的,但不再给出了一个访问冲突。
所以我的大问题是为什么?
为什么this指针不指向它应该指向的位置(?)?
为什么像上面那样向继承添加virtual可以解决它(尽管这仍然指向&getPrimaryInstance()之外的其他地方)?
这是一个错误吗?有人可以在非 MS 环境中尝试吗?
最重要的是:这安全吗??当然它不会再抱怨了,但我仍然担心它没有做它应该做的。:S
如果有人对此有所了解或经验并且可以帮助我理解它,我将非常感激,因为我仍在学习 C++,这完全超出了我目前的知识范围。
你使用 C 类型转换会害死你。
在多重继承的情况下,它特别容易崩溃。
您需要使用dynamic_cast<>来向下转换类层次结构。虽然您可以使用 static_cast<> 向上移动(就像我所做的那样),但有时我认为使用dynamic_cast<> 向两个方向移动会更清晰。
C++ 有 4 种不同类型的强制转换,旨在替代 C 风格的强制转换。您正在使用与reinterpret_cast<> 等效的内容,并且使用不正确(任何优秀的C++ 开发人员在看到reinterpret_cast<> 时都会在这里稍等片刻)。
~A3(){
if(this != (A3*)(A2*)&getPrimaryInstance())
getPrimaryInstance().unregInst(this);
}
Should be:
~A3()
{
if (this != dynamic_cast<A3*>(static_cast<A2*>(&getPrimaryInstance())))
{ getPrimaryInstance().unregInst(this);
}
}
Run Code Online (Sandbox Code Playgroud)
A2 对象的布局:(大概是这样的)。
Offset Data
A2 0x00 |------------------
0x10 * A3 Stuff
*------------------
0x20 * A1 Stuff
*------------------
0x30 * A2 Stuff
Run Code Online (Sandbox Code Playgroud)
在 getPrimaryInstance() 中
// Lets assume:
std::cout << primary; // has an address of 0xFF00
Run Code Online (Sandbox Code Playgroud)
返回的引用将指向对象的 A1 部分:
std::cout << &getPrimaryInstancce();
// Should now print out 0xFF20
Run Code Online (Sandbox Code Playgroud)
如果您使用 C 风格的强制转换,它不会检查任何内容,只会更改类型:
std::cout << (A2*)&getPrimaryInstancce();
// Should now print out 0xFF20
std::cout << (A3*)(A2*)&getPrimaryInstancce();
// Should now print out 0xFF20
Run Code Online (Sandbox Code Playgroud)
不过,如果您使用 C++ 转换,它应该正确补偿:
std::cout << static_cast<A2*>(&getPrimaryInstance());
// Should now print out 0xFF00
std::cout << dynamic_cast<A3*>(static_cast<A2*>(&getPrimaryInstance()));
// Should now print out 0xFF10
Run Code Online (Sandbox Code Playgroud)
当然,实际值都非常依赖于编译器,并且取决于实现布局。以上只是可能发生的情况的一个示例。
尽管正如所指出的,对当前正在被销毁的对象调用dynamic_cast<>可能并不安全。
那么怎么样
更改 regInst() 以便它为注册的第一个对象返回 true。getPrimaryInstance() 将始终由第一个要创建的对象创建,因此它始终是第一个注册自身的对象。
将此结果存储在本地成员变量中,并且只有在您不是第一个时才取消注册:
A3()
{
m_IamFirst = getPrimaryInstance().regInst(this);
}
~A3()
{
if (!m_IamFirst)
{
getPrimaryInstance().unregInst(this);
}
}
Run Code Online (Sandbox Code Playgroud)
问题:
为什么 this 指针没有指向它应该指向的地方(?)?
确实如此。只需使用 C-Cast 螺丝固定指针即可。
为什么像上面那样向继承添加 virtual 可以解决这个问题(尽管它仍然指向 &getPrimaryInstance() 之外的其他地方)?
因为它改变了内存中的布局,使得 C-Cast 不再搞乱你的指针。
这是一个错误吗?
No. C-Cast 的错误使用
有人可以在非 MS 环境中尝试一下吗?
它会做类似的事情。
最重要的是:这安全吗?
不。虚拟成员的布局由实现定义。你只是碰巧很幸运。
解决方案:停止使用 C 风格的强制转换。使用适当的 C++ 转换。
| 归档时间: |
|
| 查看次数: |
1681 次 |
| 最近记录: |