“this”指针可以与对象指针不同吗?

16

我最近在某个类中发现了这个奇怪的函数:

void* getThis() {return this;}
在代码的后面,有时会像这样使用:bla->getThis()(其中bla是指向定义了该函数的类的对象的指针)。
我无法理解这有什么作用。在某些情况下,指向对象的指针是否与对象的this不同(其中bla != bla->getThis())?
这似乎是一个愚蠢的问题,但我想知道我是否漏掉了什么。

6
看起来是要转换成void*类型。为什么他们不直接转换成void*,我不知道。在别人的代码中有时候会看到一些有趣的东西。 - Dave
是的,总的来说,强制类型转换是我能想到的唯一方法。 - Panzercrisis
5
@Dave 是的,总是在别人的代码里,从不在我们自己的代码里(当然了)。;) - syam
@MooingDuck - 是的。这里涉及到模板和继承。每个都如何影响情况? - kipod
4
我从不打开一个月前的代码文件。我的代码非常稳定。 - JustSid
显示剩余6条评论
5个回答

17

当然,指针值可能不同!下面是一个演示此问题的示例(您可能需要在系统上使用derived1而不是derived2来获得差异)。重点是,当涉及到虚拟继承和多重继承时,this指针通常会进行调整。这可能是一个罕见的情况,但确实发生过。

此习语的一个潜在用例是能够将已知类型的对象存储为void const*(或void*; 在这里const正确性无关紧要)并在需要时还原它们:如果您有一个复杂的继承层次结构,您不能仅仅将任何奇怪的指针转换为void*并希望能够将其还原为原始类型!也就是说,在下面的示例中轻松获取例如指向base的指针并将其转换为void*,您将调用p->getThis(),这样比static_cast<base*>(p)并获得可以安全转换为base*void*更容易,使用static_cast<base*>(v)进行静态转换:您可以反转隐式转换,但只能在将指针强制转回原始类型的确切类型时进行。也就是说,static_cast<base*>(static_cast<void*>(d)),其中d是指向派生自base类型对象的指针是非法的,但static_cast<base*>(d->getThis())合法。

现在,为什么地址首先会发生变化呢?在下面的示例中,base是两个派生类的虚基类,但可能会有更多。所有虚继承自base的类的子对象将在进一步派生类的对象中共享一个公共base主题(在下面的示例中为concrete)。此base子对象的位置相对于各自派生的子对象可能不同,具体取决于如何排序不同的类。因此,指向base对象的指针通常与虚继承自base的类的子对象的指针不同。相关偏移量将在编译时计算(如果可能),或者来自类似于运行时vtable的东西。在沿着继承层次结构转换指针时,这些偏移量会进行调整。

#include <iostream>

struct base
{
    void const* getThis() const { return this; }
};

struct derived1
    : virtual base
{
    int a;
};

struct derived2
    : virtual base
{
    int b;
};

struct concrete
    : derived1
    , derived2
{
};

int main()
{
    concrete c;
    derived2* d2 = &c;
    void const* dptr = d2;
    void const* gptr = d2->getThis();
    std::cout << "dptr=" << dptr << " gptr=" << gptr << '\n';
}

2
@Dave:正如Steve Clamage很久以前正确指出的那样,“一个混淆的C++竞赛就像在桶里打鱼”!然而,这种显式转换确实有真正的用途:您可以将从getThis()获得的指针转换为base const*。您无法使用从d2获取的指针通过将其转换为void const*来执行相同的操作(但是,您可以将从这样获得的指针转换为derived2 const*,即撤消隐式转换)。 - Dietmar Kühl
+1 是因为多重继承是我能想到的唯一场景,也许这样做有些道理。尽管我想不出实际用途,但如果有一件事情我真正知道的是我一无所知。 :) - syam
@DietmarKühl 哦,我能理解这个方法的目的。但我的观点是,在这种情况下,它应该被命名为 getBase 或类似的名称,而不是 getThis - Dave
1
@Dietmar Kühl - 哇...您能否在答案中解释一下为什么会这样?我可以看到结果,但仍然不明白为什么会发生...谢谢。 - Jimbo
@Dave:嗯...假设使用getThis()的代码恰好是通用的,即模板化的代码:通用代码确实喜欢使用一致的名称,如getThis(),而不是有时使用getWidget(),有时使用getGadget() - Dietmar Kühl
1
@Jimbo:我已经编辑了我的答案,加入了动机。所有人:如果还有关于这个问题的疑问,请让我知道,我会尽力解决! - Dietmar Kühl

2

不可以。 在有限的情况下是可以的。

这看起来像是受 Smalltalk 启发的东西,在其中所有对象都有一个 yourself 方法。可能有一些情况下使用它会使代码更加清晰。正如评论所指出的,这似乎是在 c++ 中实现这种习惯用法的一种奇怪方式。

在您的特定情况下,我建议使用 grep 查找该方法的实际用法,以了解它是如何被使用的。


+1 仅适用于 grep 部分。老实说,在我20多年的C++编程经验中,我从未遇到过需要使用这种结构的情况,但也许这只是我的个人经验。 - syam
@syam 我想不出一个例子,但是我对Smalltalk的经验有限。我认为它在那种语言中成为一种事物是有原因的,这个原因可能适用于C++,也可能不适用。 - Marcin
使用 void* p0 = bla; void* p1 = bla->getThis(); 可能会出现 p0 != p1 的情况!这只需要 bla 是指向适当构造类型的指针即可。 - Dietmar Kühl
1
@DietmarKühl 已经注意到了。我仍然不知道实际用途是什么。 - Marcin

1
你的类可以有自定义的operator&(因此&a可能不会返回athis)。这就是为什么存在std::addressof的原因。

0

我在许多(很多很多)年前遇到过类似的东西。如果我记得正确的话,当一个类正在操作同一类的其他实例时需要它。一个例子可能是一个容器类,它可以包含自己的类型/(类?)。


0

这可能是覆盖this关键字的一种方法。 假设您有一个内存池,在程序开始时完全初始化,例如您知道随时可以处理最多50个消息CMessage。 您创建一个大小为50 * sizeof(CMessage)(无论这个类是什么)的池,并且CMessage实现了getThis函数。

这样,您只需覆盖“this”以访问池,而不是覆盖new关键字。 这也意味着对象可能在不同的内存空间上定义,例如在SRAM上,在引导模式下,然后在SDRAM上。

在这种情况下,当被覆盖时,相同的实例可能会在整个程序中返回不同的getThis值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接