gpt4 book ai didi

c++ - 为什么 "this"在具有多个基类的类的父类中发生变化?

转载 作者:行者123 更新时间:2023-12-01 13:48:25 25 4
gpt4 key购买 nike

(初始注意:这个问题与删除 void 指针是否安全不是同一个问题,尽管该问题与更新 2 中确定的问题有一些关系。这里的问题是为什么基类获得不同的值from this 比由派生类为同一个对象获取。如果派生对象会调用基类的自杀方法,基类必须有一个虚析构函数,被删除的指针必须是类型指向基类的指针;将其存储在 void* 中并不是从基类方法中删除对象的安全方法。)

我有一个菱形多重继承,其中我的子类有两个父类,它们都继承自同一个祖父类,因此:

class Grand
class Mom : public virtual Grand
class Dad : public Grand
class Child : Mom, Dad

我写了 MomChild ,但是 GrandDad是我没有写的库类(这就是为什么 Mom 实际上从 Grand 继承,但 Dad 没有)。
Mom实现在 Grand 中声明的纯虚方法. Dad才不是。因此, Child也实现了相同的方法(否则编译器会反对 Dad 对该方法的声明,由 Child 继承,没有实现)。 Child的实现仅调用 Mom的实现。这是代码(我已经包含了 DadGrand 的代码,因为这是一个 SSCCE,而不是我坚持使用的代码,它依赖于我没有编写的库类):

class Grand
{
public:
virtual void WhoAmI(void) = 0;
};

class Mom : public virtual Grand
{
public:
virtual void WhoAmI()
{
void* momThis = this;
}

//virtual int getZero() = 0;
};

class Dad : public Grand
{
};

class Child : Mom, Dad
{
public:
void WhoAmI()
{
void* childThis = this;
return Mom::WhoAmI();
}

int getZero()
{
return 0;
}
};

int main()
{
Child* c = new Child;

c->WhoAmI();
return 0;
}

请注意 getZero Child 中的方法永远不会被调用。

使用调试器逐步执行,我看到 Child* c 中的地址是 0x00dcdd08 .走进 Child::WhoAmI ,我看到地址在 void* childThis也是 0x00dcdd08 ,这正是我所期望的。进一步进入 Mom::WhoAmI ,我看到了 void* momThis已分配 0x00dcdd0c ,我将其解释为 Mom 的地址我多重继承的子对象 Child对象(但我承认我在这一点上有点超出我的深度)。

好的,事实是 ChildthisMomthis不同并不令我震惊。这是什么:如果我取消注释 getZero 的声明在 Mom ,并再次执行所有这些操作, Mom::thisChild::this是相同的!

怎么能加 virtual int getZero() = 0Mom类(class)成绩为 Mom子对象和 Child具有相同地址的对象?我想也许编译器可以识别所有 Mom的方法是虚拟的,它的 vtable 与 Child 相同's,所以它们以某种方式变成了“相同”的对象,但是向每个类添加更多不同的方法并不会改变这种行为。

谁能帮我理解什么时候管理 this多重继承 child 的 parent 和 child 是否相同,何时不同?

更新

我试图简化事情,尽可能将注意力集中在何时 this 的问题上。父对象中的值与其在该父对象的子对象中的值不同。为此,我更改了继承以使其成为真正的钻石,使用 DadMom两者实际上都继承自 Grand .我已经消除了所有虚拟方法,不再需要指定我正在调用哪个父类的方法。相反,我在每个父类中都有一个独特的方法,可以让我使用调试器查看值 this在每个 parent 对象中都有。我看到的是 this对一个 parent 和 child 来说是一样的,但对另一个 parent 来说是不同的。此外,当子级声明中父级的顺序发生变化时,哪个父级具有不同的值也会发生变化。

如果任何一个父对象试图删除自己,结果就会产生灾难性的后果。这是在我的机器上运行良好的代码:

class Grand
{
};

class Mom : public virtual Grand
{
public:
void WhosYourMommy()
{
void* momIam = this; // momIam == 0x0137dd0c
}
};

class Dad : public virtual Grand
{
public:
void WhosYourDaddy()
{
void* dadIam = this; // dadIam == 0x0137dd08
delete dadIam; // this works
}
};

class Child : Dad, Mom
{
public:
void WhoAmI()
{
void* childThis = this;

WhosYourMommy();
WhosYourDaddy();

return;
}
};

int main()
{
Child* c = new Child; // c == 0x0137dd08

c->WhoAmI();

return 0;
}

但是,如果我更改 class Child : Dad, Momclass Child : Mom, Dad ,它在运行时崩溃:

class Grand
{
};

class Mom : public virtual Grand
{
public:
void WhosYourMommy()
{
void* momIam = this; // momIam == 0x013bdd08
}
};

class Dad : public virtual Grand
{
public:
void WhosYourDaddy()
{
void* dadIam = this; // dadIam == 0x013bdd0c
delete dadIam; // this crashes
}
};

class Child : Mom, Dad
{
public:
void WhoAmI()
{
void* childThis = this;

WhosYourMommy();
WhosYourDaddy();

return;
}
};

int main()
{
Child* c = new Child; // c == 0x013bdd08

c->WhoAmI();

return 0;
}

当您的类包含可以删除该类的对象的方法(“自杀方法”),并且这些方法可能会从派生类中调用时,这是一个问题。

但是,我想我已经找到了解决方案:任何包含一个方法的基类,该方法可能会删除自身的实例,并且可能从该类派生的类的实例中调用这些方法,必须有一个虚拟析构函数。

在上面的代码中添加一个会使崩溃消失:

class Grand
{
};

class Mom : public virtual Grand
{
public:
void WhosYourMommy()
{
void* momIam = this; // momIam == 0x013bdd08
}
};

class Dad : public virtual Grand
{
public:
virtual ~Dad() {};

void WhosYourDaddy()
{
void* dadIam = this; // dadIam == 0x013bdd0c
delete dadIam; // this crashes
}
};

class Child : Mom, Dad
{
public:
void WhoAmI()
{
void* childThis = this;

WhosYourMommy();
WhosYourDaddy();

return;
}
};

int main()
{
Child* c = new Child; // c == 0x013bdd08

c->WhoAmI();

return 0;
}

我遇到的许多人都对对象删除自身的想法感到震惊,但在实现 COM 的 IUnknown::Release 方法时,这是合法的和必要的习惯用法。我找到了 good guidelines关于如何使用 delete this安全,有些同样安全 good guidelines关于使用虚拟析构函数来解决这个问题。

但是,我注意到,除非编写您的父类的人使用虚拟析构函数对其进行编码,否则从从该父类派生的类的实例中调用该父类的任何自杀方法都可能会崩溃,并且这样做是不可预测的。也许是包含虚拟析构函数的一个原因,即使您认为不需要它。

更新 2

好吧,如果您在 Dad 中添加一个虚拟析构函数,问题又会回来。和 Mom .此代码在尝试删除 Dad 时崩溃的 this指针,不匹配 Childthis指针:

class Grand
{
};

class Mom : public virtual Grand
{
public:
virtual ~Mom() {};

void WhosYourMommy()
{
void* momIam = this; // momIam == 0x013bdd08
}
};

class Dad : public virtual Grand
{
public:
virtual ~Dad() {};

void WhosYourDaddy()
{
void* dadIam = this; // dadIam == 0x013bdd0c
delete dadIam; // this crashes
}
};

class Child : Mom, Dad
{
public:
virtual ~Child() {};

void WhoAmI()
{
void* childThis = this;

WhosYourMommy();
WhosYourDaddy();

return;
}
};

int main()
{
Child* c = new Child; // c == 0x013bdd08

c->WhoAmI();

return 0;
}

更新 3

感谢 BeyelerStudios 提出正确的问题:删除 void*而不是删除 Dad*阻止 C++ 知道它真正删除的是什么,因此阻止它调用基类和派生类的虚拟析构函数。更换 delete dadIamdelete this解决了这个问题,代码运行良好。

虽然说起来有些可笑,但是替换 delete dadIamdelete (Dad*)dadIam也运行良好,并有助于说明 delete 操作的指针类型有什么不同 delete做。 (在多态语言中我应该不会感到惊讶。)

BeyelerStudios,如果您想将其发布为答案,我会为您选中该框。

谢谢!

最佳答案

正如标准 [intro.object] 所提到的:

Objects can contain other objects, called subobjects. A subobject can be [...] a base class subobject [...].



此外[expr.prim.this]:

The keyword this names a pointer to the object for which a non-static member function is invoked [...].



不用说,两个不同的类(派生类和基类)是不同的对象,因此 this 可以有不同的值。指针。

Can anyone help me understand what governs when this is the same for the parent and child of a multiply inherited child and when it is different?



它们何时以及为何不同并不受标准的约束(当然,这主要是由于与对象关联的虚表的存在,但请注意,虚表只是处理多态性的一种常见、便捷的方式,标准从未提及他们)。
它通常源自选定/实现的 ABI(有关通用 ABI 的更多详细信息,请参阅 here)。

它遵循一个最小的工作示例来重现该案例:
#include<iostream>

struct B {
int i;
void f() { std::cout << this << std::endl; }
};

struct D: B {
void f() { std::cout << this << std::endl; }
virtual void g() {}
};

int main() {
D d;
d.f();
d.B::f();
}

一个示例输出是:

0xbef01ac0
0xbef01ac4

关于c++ - 为什么 "this"在具有多个基类的类的父类中发生变化?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38267148/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com