为什么this-pointer地址不是析构函数中预期的(c ++)

时间:2010-12-07 15:00:59

标签: c++ destructor multiple-inheritance this-pointer

我对基类析构函数中的this-pointer有一个奇怪的问题。

问题描述:

我有3个班级: A1 A2 A3

A2 A1 公开继承,并从 A3

私下继承
class A2:private A3, public A1 {...}

A3 有一个函数 getPrimaryInstance() ...返回 A1 类型的引用 一个 A2 实例:

A1& A3::getPrimaryInstance()const{
    static A2 primary;
    return primary;
}

A3 构造函数如下所示:

A3(){
    getPrimaryInstance().regInst(this);
}

(其中 regInst(...) A1 中定义的函数,用于存储指向所有 A3 实例的指针)

类似于 A3 析构函数:

~A3(){
    getPrimaryInstance().unregInst(this);
}

^这是问题发生的地方!

当在程序终止时销毁名为 primary 的静态 A2 -instance时,将调用 A3 -destructor,但在内部~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);
}

(双重演员的原因是继承:) A1 A3
。 \ /
。 A2
(但这并不重要,可能只有(int) -casted或其他什么)

踢球者是它仍然崩溃。 使用调试器逐步执行代码可以发现,当我的 A2 主要 -instance在析构函数中获取 this -pointer并获取地址时来自 getPrimaryInstance()的调用由于某种原因根本不匹配!我无法理解为什么 this -pointer指向的地址总是与它(对于我有限的知识)应该是的地址不同。 :(

在析构函数中执行此操作:

int test = (int)this - (int)&getPrimaryInstance();

同时向我展示了差异并不是恒定的(我简单地得到了一个理论,即存在一些常数偏移),所以当它应该是同一个时,它就像它是两个完全不同的对象。 :(

我在VC ++ Express(2008)中编码。 谷歌搜索后,我发现了以下MS文章:
FIX: The "this" Pointer Is Incorrect in Destructor of Base Class

这与我所遇到的问题不同(并且它在C ++中也被认为是固定的.Net 2003)。但无论症状看起来是多么相似,他们确实提出了一个简单的解决方法,所以我决定尝试一下:
删除了not-working- if -statement,并在第二次继承之前的虚拟中添加到 A2 ,如下所示:

class A2:private A3, public A1 {...} // <-- old version
class A2:private A3, virtual public A1 {...} //new, with virtual!

和它一起工作! -pointer仍然看似错误,但不再提供访问冲突。

所以我的大问题是为什么?
为什么 this -pointer没有指向应该的位置(?)?
为什么在上面的继承中添加虚拟来解决它(尽管这个仍然指向除&amp; getPrimaryInstance()之外的其他地方)?
这是一个错误吗?有人可以在非MS环境中试用它吗? 最重要的是:这样安全吗?当然它不再抱怨了,但我仍然担心它不会做它应该做的事情。 :S

如果有人对此有所了解或有经验并且可以帮助我理解它,我会非常感激,因为我还在学习C ++,这完全超出了我目前的知识。

8 个答案:

答案 0 :(得分:4)

你使用C演员阵容会杀了你 在多重继承的情况下,它尤其容易中断。

您需要使用dynamic_cast&lt;&gt;抛弃一个类层次结构。虽然你可以使用static_cast&lt;&gt;向上移动(正如我所做的那样)但有时我认为使用dynamic_cast&lt;&gt;更清晰向两个方向移动。

避免C ++代码中的C样式转换

C ++有4种不同类型的演员,旨在取代C风格演员。您使用的是等效的reinterpret_cast&lt;&gt;并且您使用不正确(任何优秀的C ++开发人员在看到reinterpret_cast&lt;&gt;将会在这里停留一段时间)。

~A3(){
    if(this != (A3*)(A2*)&getPrimaryInstance())
        getPrimaryInstance().unregInst(this);
}

Should be:

~A3()
{
   if (this != dynamic_cast<A3*>(static_cast<A2*>(&getPrimaryInstance())))
   {    getPrimaryInstance().unregInst(this);
   }
}

A2对象的布局:(可能是这样的)。

     Offset   Data
A2   0x00   |------------------
     0x10   * A3 Stuff
            *------------------
     0x20   * A1 Stuff
            *------------------
     0x30   * A2 Stuff

在getPrimaryInstance()

 // Lets assume:
 std::cout << primary; // has an address of 0xFF00

返回的引用将指向对象的A1部分:

std::cout << &getPrimaryInstancce();
// Should now print out 0xFF20

如果使用C样式转换,则不会检查任何内容只是更改类型:

std::cout << (A2*)&getPrimaryInstancce();
// Should now print out 0xFF20
std::cout << (A3*)(A2*)&getPrimaryInstancce();
// Should now print out 0xFF20

虽然如果使用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

当然,实际值都非常依赖于编译器,并且取决于实现布局。以上只是可能发生的事情的一个例子。

虽然正如所指出的那样调用dynamic_cast&lt;&gt;可能并不安全。在一个目前处于被摧毁过程中的物体上。

那怎么样?

更改regInst(),使其对注册的第一个对象返回true。 getPrimaryInstance()将始终由第一个要创建的对象创建,因此它始终是第一个自我注册的对象。

将此结果存储在本地成员变量中,如果不是第一个,则仅取消注册:

A3()
{
    m_IamFirst = getPrimaryInstance().regInst(this);
}

~A3()
{
    if (!m_IamFirst)
    {
         getPrimaryInstance().unregInst(this);
    }
}

问题:

  

为什么this-pointer不指向它应该的位置?(?)?

确实如此。只需使用C-Cast螺丝钉上指针。

  

为什么像上面那样为继承添加虚拟解决它(尽管这仍然指向除&amp; getPrimaryInstance()之外的其他地方?)

因为它改变了内存中的布局,使得C-Cast不再搞砸你的指针。

  

这是一个错误吗?

没有。 C-Cast

的使用不正确
  

有人可以在非MS环境中试用吗?

它会做类似的事情。

  

最重要的是:这样安全吗?

没有。实现定义了虚拟成员的布局方式。你碰巧很幸运。

解决方案:停止使用C样式转换。使用适当的C ++强制转换。

答案 1 :(得分:3)

class A2 : private A3, public A1 {...} // <-- old version
class A2 : private A3, virtual public A1 {...} //new, with virtual!  
  

为什么像上面那样为继承添加虚拟解决它(尽管这仍然指向除&amp; getPrimaryInstance()之外的其他地方?)

这会产生影响的原因是virtual继承会影响基类构造函数和基类析构函数的调用顺序。

this指针的数值不相同的原因是完整对象A2 primary;的不同“基类子对象”可以且必须具有不同的地址。在调用任何析构函数之前,您可以使用dynamic_cast来介于A1*A2*之间。当您某些 A3对象确实是A2的私有基础部分时,您可以使用C风格的转换从A3*转到A2* ~A2

但是,一旦析构函数~A3的主体完成,dynamic_cast析构函数就是这种情况,A1*A2*A3*的{​​{1}}将会失败,从A2*A2的C样式转换将产生未定义的行为,因为不再有任何A2 primary;对象。

因此,除非您更改{{1}}的存储/访问方式,否则可能无法执行您正在尝试的操作。

答案 2 :(得分:2)

如果你有一个钻石继承结构,那么虚拟基类应该只能起作用,即你要多次继承共享一个公共基类的类。您是否在实际代码中显示了A1,A2和A3的整个实际继承树?

答案 3 :(得分:1)

问题可能是当为A2对象调用A3 :: ~A3()时,A2对象已被破坏,因为在A2的销毁结束时调用~A3()。你不能再次调用getPrimary,因为它已经被破坏了。 注意:这适用于静态变量primary

的情况

答案 4 :(得分:1)

OP @AnorZaken评论说:

  

...这是我试图解决的原始问题之一:我希望getPrimaryInstance()直接返回A2引用,但我不能! A3还没见过A2的定义!由于getPrimaryInstance()是在A3的基类(未在上面提到)中声明的,因此您得到:错误C2555:'A3 :: getPrimaryInstance':覆盖虚函数返回类型不同且与'A3Base :: getPrimaryInstance'不协变简单:即使我声明A2的存在我也不知道有什么方法告诉编译器A2在我声明A2之前将A1作为基础。 :(如果我能解决这个问题,那就太棒了!

所以听起来你有类似的东西:

class A3Base {
public:
  virtual A1& getPrimaryInstance();
};

由于class A2之前无法定义class A3,我只想跳过协变返回类型。如果您需要从A2&获取A3引用的方法,请将其添加为不同的方法。

// A3.hpp
class A2;

class A3 : public A3Base {
public:
  virtual A1& getPrimaryInstance();
  A2& getPrimaryInstanceAsA2();
};

// A3.cpp
#include "A3.hpp"
#include "A2.hpp"

A1& A3::getPrimaryInstance() {
    return getPrimaryInstanceAsA2(); // no cast needed for "upward" public conversion
}

答案 5 :(得分:0)

  

当在程序终止时销毁名为 primary 的静态 A2 -instance时,将调用A3析构函数,但在~3A内我尝试访问一个函数我破坏的同一个例子。 =&GT;在运行时访问冲突!

当静态 A2 - 名为的实例 已销毁 指向的指针时>将指向内存中的“随机”位置。因此,您尝试访问随机内存位置,并获得运行时违规。这一切都与您调用析构函数的顺序有关,并在析构函数中进行调用。

尝试这样的事情:

delete a3;
delete a2-primary;

而不是

delete a2-primary;
delete a3;

我也认为您可能会发现 typecasting tutorial 很有帮助。

希望我能帮助你。

答案 6 :(得分:0)

你应该问两个更大的问题:

  1. 为什么我需要使用私有继承?
  2. 为什么我需要对非抽象基类使用多重继承?
  3. 在大多数情况下,#1的答案是你没有。将包含数据成员的类声明为另一个类通常会在更清晰,更易于维护的代码库中处理相同的情况。

    在大多数情况下,#2的答案也是如此。这是您使用该语言的一项功能。

    我建议您阅读Meyers书籍并重新评估您的设计模式。

答案 7 :(得分:-1)

简短回答。它发生了,因为不正确的析构函数正在调用。 如需长时间回答,请检查thisthis 并查看Scott Meyer的Effective C ++。它涵盖了这些问题。