这种指针调整是否发生在非多态继承中?

Does this pointer adjustment occur for non-polymorphic inheritance?

非多态继承需要这个指针调整吗?在我见过的所有情况下,这个指针调整都讨论了使用的示例涉及通过关键字 virtual.

的多态继承

我不清楚非多态继承是否需要此指针调整。

一个极其简单的例子是:

struct Base1 {
    void b1() {}
};

struct Base2 {
    void b2() {}
};

struct Derived : public Base1, Base2 {
    void derived() {}
};

下面的函数调用是否需要调整指针?

Derived d;
d.b2();

在这种情况下,this 指针调整显然是多余的,因为没有访问任何数据成员。另一方面,如果继承的函数访问数据成员,那么这种指针调整可能是个好主意。另一方面,如果成员函数没有内联,那么这个指针调整似乎无论如何都是必要的。

我知道这是一个实现细节而不是 C++ 标准的一部分,但这是一个关于真实编译器行为方式的问题。我不知道这是不是像 vtables 这样所有编译器都遵循相同的通用策略的情况,或者我是否问了一个非常依赖于编译器的问题。如果它非常依赖于编译器,那么这本身就是一个足够的答案,或者如果您愿意,您可以专注于 gcc 或 clang。

这不仅是特定于编译器的,也是特定于优化级别的。根据经验,所有 this 指针都会进行调整,只是有时它会像许多编译器中的示例一样调整为 0(但绝对不是所有 - IIRC,MSVC 是一个明显的例外)。如果函数是内联的并且不访问 this,那么调整可能会被完全优化掉。

对象布局未按语言指定。来自 C++ 草案标准 N3337:

10 Derived Classes

5 The order in which the base class subobjects are allocated in the most derived object (1.8) is unspecified. [ Note: a derived class and its base class subobjects can be represented by a directed acyclic graph (DAG) where an arrow means “directly derived from.” A DAG of subobjects is often referred to as a “subobject lattice.”

6 The arrows need not have a physical representation in memory. —end note ]

回答你的问题:

Would the following function call require this pointer adjustment?

这取决于编译器如何创建对象布局。可能会也可能不会。

在你的例子中,由于classes中没有成员数据,所以没有虚成员函数,而你使用的是第一个基class的成员函数,你可能不会看到任何指针调整。但是,如果你添加成员数据,并使用第二个基的成员函数class,你很可能会看到指针调整。

下面是一些示例代码和 运行 代码的输出:

#include <iostream>

struct Base1 {
   void b1()
   {
      std::cout << (void*)this << std::endl;
   }
   int x;
};

struct Base2 {
   void b2()
   {
      std::cout << (void*)this << std::endl;
   }
   int y;
};

struct Derived : public Base1, public Base2 {
   void derived() {}
};

int main()
{
   Derived d;
   d.b1();
   d.b2();
   return 0;
}

输出:

0x28ac28
0x28ac2c

用R Sahu的方法测试了一下,貌似gcc,clang,icc的答案是yes,出现这个指针调整,除非baseclass是primary baseclass或空基 class.

测试代码:

#include <iostream>

namespace {
struct Base1
{
    void b1()
    {
        std::cout << "b1() " << (void*)this << std::endl;
    }

    int x;
};

struct Base2
{
    void b2()
    {
        std::cout << "b2() " << (void*)this << std::endl;
    }

    int x;
};

struct EmptyBase
{
    void eb()
    {
        std::cout << "eb(): " << (void*)this << std::endl;
    }
};

struct Derived : private Base1, Base2, EmptyBase
{
    void derived()
    {
        b1();
        b2();
        eb();
        std::cout << "derived(): " << (void*)this << std::endl;
    }
};
}

int main()
{
    Derived d;
    d.derived();
}

匿名命名空间用于提供基础 classes 内部链接。智能编译器可以确定基本 classes 的唯一用途是在这个翻译单元中,这个指针调整是不必要的。私有继承是一个很好的衡量标准,但我认为它没有真正的意义。

示例 g++ 4.9.2 输出:

b1() 0x7fff5c5337d0
b2() 0x7fff5c5337d4
eb(): 0x7fff5c5337d0
derived(): 0x7fff5c5337d0

示例 clang 3.5.0 输出

b1() 0x7fff43fc07e0
b2() 0x7fff43fc07e4
eb(): 0x7fff43fc07e0
derived(): 0x7fff43fc07e0

示例 icc 15.0.0.077 输出:

b1() 0x7fff513e76d8
b2() 0x7fff513e76dc
eb(): 0x7fff513e76d8
derived(): 0x7fff513e76d8

所有三个编译器都为 b2() 调整了 this 指针。如果他们在这种简单的情况下不省略 this 指针调整,那么他们很可能永远不会省略 this 指针调整。主碱基 class 和空碱基 class 是例外。

据我所知,符合智能标准的编译器可以省略 b2() 的 this 指针调整,但这只是他们不做的优化。