2

非多态继承需要这种指针调整吗?在我看到的所有情况下,指针调整都讨论了使用的示例涉及通过关键字进行多态继承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。

4

3 回答 3

4

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

10个派生类

5 未指定基类子对象在最派生对象 (1.8) 中的分配顺序。[注意:派生类及其基类子对象可以用有向无环图(DAG)表示,其中箭头表示“直接派生自”。子对象的 DAG 通常称为“子对象晶格”。</p>

在此处输入图像描述

6 箭头不需要在内存中有物理表示。——尾注]

来到你的问题:

以下函数调用是否需要此指针调整?

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

在您的情况下,由于类中没有成员数据,没有虚拟成员函数,并且您正在使用第一个基类的成员函数,因此您可能不会看到任何指针调整。但是,如果您添加成员数据,并使用第二个基类的成员函数,您很可能会看到指针调整。

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

#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
于 2015-02-06T19:01:05.827 回答
1

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

于 2015-02-06T18:46:37.733 回答
0

用R Sahu的方法测试一下,gcc、clang、icc的答案好像是yes,这个指针调整会发生,除非基类是primary基类或者空基类。

测试代码:

#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();
}

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

示例 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

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

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

于 2015-02-06T20:10:44.807 回答