【问题标题】:C++ pointer multi-inheritance funC++指针多继承的乐趣
【发布时间】:2010-01-28 18:58:52
【问题描述】:

我正在编写一些涉及从基本引用计数指针类继承的代码;并且出现了一些复杂的 C++。我已将其减少如下:

假设我有:

class A{};
class B{};
class C: public A, public B {};

C c;
C* pc = &c;
B* pb = &c;
A* pa = &c;

// does pa point to a valid A object?
// does pb point to a valid B object?

// does pa == pb ?

此外,确实:

// pc == (C*) pa ?
// pc == (C*) pb ?

谢谢!

【问题讨论】:

  • 你说的 == 是什么意思?显然,地址是一样的。
  • 地址不一定相同。
  • 在我见过的多重继承(虚拟)的一种实现中,在派生类对象的基地址中添加了一些偏移量,以访问 B 的属性和方法。但这对用户来说是不可见的因为编译器在内部对其进行操作。因此在你的程序中你可能仍然会看到 A、B 和 C 的地址是相同的。
  • @hype:这里的关键词是“可能”。
  • @Dave Bacher 这并非总是如此。不久前,我个人在尝试取消引用指向对象的指针时遇到了段错误,但指针的类型与函数预期的类型不同。但是,对象继承自这两种类型。它可能有效,但并不安全。

标签: c++ multiple-inheritance


【解决方案1】:
  • pa 是否指向有效的 A 对象?
  • pb 是否指向有效的 B 对象?

是的,C* 被转换,以便 papb 指向正确的地址。

  • pa == pb 吗?

不,通常不会。同一地址不能有A 对象和B 对象。

此外,确实

  • pc == (C*) pa ?
  • pc == (C*) pb ?

强制转换将指针转换回C 对象的地址,因此两个等式都为真。

【讨论】:

  • dynamic_cast 在这里不合适,C 风格是正确的(尽管形式不好)。根据 §5.4/7,C 风格的转换可以调用 static_cast 其中“——指向非虚拟基类类型对象的指针、非虚拟基类类型的左值或指向非虚拟基类类型的成员的指针。虚拟基类类型可以分别显式转换为指针、引用或指向派生类类型成员的指针。”
  • 是的,你是对的,例如,只有在 pa 被转换为 B* 时才需要动态转换。
  • 在这种情况下,dynamic_cast 表达式只是表达NULL 的一种奇特方式。 A* 不能是 B*,所以它总是会失败。
  • @Potatoswatter:不,这会起作用,因为pa 确实是C*,而C* 可以转换为B*。但要弄清楚这一点需要dynamic_cast
  • @Skizz:默认情况下假设基类不为空。新手不需要被关于他的例子的技术性误导。 pa == pb 是未定义的,无论如何答案都不是肯定的。
【解决方案2】:

C++ Common Knowledge: Essential Intermediate Programming中的Item 28 Meaning of Pointer Comparison)解释了C++中对象指针的关键:

在 C++ 中,一个对象可以有多个有效地址,并且指针比较不是关于地址的问题。这是一个关于对象身份的问题。

看一下代码:

class A{};
class B{};
class C: public A, public B {};

C c;
C* pc = &c;
B* pb = &c;
A* pa = &c;

class C 派生自class Aclass B,所以class C 既是class A 又是class B。对象 C c 有 3 个有效地址:class Aclass Bclass C 的地址。实现依赖于编译器,所以不能假设class C的内存布局,可能是这样的:

 ----------  <- pc (0x7ffe7d10e1e0)
 |        |
 ----------  <- pa (0x7ffe7d10e1e4)
 | A data |
 ----------  <- pb (0x7ffe7d10e1e8)
 | B data |
 ----------
 | C data |
 ----------

在上述情况下,虽然pcpapb的地址值不一样,但它们都指向同一个对象(c),所以编译器必须保证pc比较等于papb,即pc == papc == pb。编译器通过将要比较的指针之一的值调整适当的偏移量来完成此比较。例如,

pc == pa

翻译成:

pc ? ((uintptr_t)pc + 4 == (uintptr_t)pa) : (pa == 0)

另外,由于AB没有继承关系,我们不能直接比较papb

对于您的问题:

(1) does pa point to a valid A object?  
(2) does pb point to a valid B object?  
Yes, refer the above diagram. 

(3) pc == (C*) pa ?  
(4) pc == (C*) pb ?  
Yes, No need to add (C*).

(5) does pa == pb ?
No. We can't compare them.

【讨论】:

  • (6) (C*)pa == (C*)pb ?
【解决方案3】:

C 嵌入了 AB

class C: public A, public B {};

与 C 代码非常相似

struct C {
    A self_a;
    B self_b;
};

如果您使用的是直接 C,(B*) &amp;c; 等效于 static_cast&lt; B* &gt;( &amp;c ) 类似于 &amp;c.self_b

一般来说,您不能依赖指向不同类型的指针是可互换或可比较的。

【讨论】:

    【解决方案4】:
    pc == pa;
    pc == pb;
    

    未定义,取决于类结构。

    pc == (C*) pa;
    pc == (C*) pb;
    

    没关系。

    pa == pb;
    

    没有。

    它们是否指向有效对象?

    Yes
    

    【讨论】:

      【解决方案5】:

      你在内存中得到的是这样的东西

       ----------
       | A data |
       ----------
       | B data |
       ----------
       | C data |
       ----------
      

      因此,如果您想要整个 C 对象,您将获得指向内存开头的指针。如果您只想要 A“部分”,您将获得相同的地址,因为那是数据成员所在的位置。如果你想要 B “部分”,你会得到开头 + sizeof(A) + sizeof(编译器为 vtable 添加的任何内容)。 因此,在示例中,pc != pb(可能是 pc != pa)但 pa 永远不等于 pb。

      【讨论】:

      • 我对@9​​87654323@ 表示不同意见。将问题的代码键入 DevStudio 2005,我得到所有具有相同值的指针。这可能就是 C++ 标准所说的应该发生的事情,在这种情况下 - 没有虚拟方法和数据成员。 OP 发布了另一个问题,其中基类确实有成员,因此有不同的答案,或多或少就是这个答案。
      • @Skizz:您所看到的称为空基类优化google.com/search?q=empty+base+class+optimization,它已被所有现代编译器实现,但远非必需。
      • A 类{}; B类{}; C类:公共A,公共B{}; int main() { C c; C* pc(&c); A* pa(pc); B* 铅(pc); printf("C=0x%08X\nB=0x%08X\nA=0x%08X\n", pc, pb, pa); VS2008 Release build 的输出 C=0x0018FEFF B=0x0018FF00 A=0x0018FEFF 如果更改 C 从 A 和 B 继承的顺序,您将得到 A 和 B 的不同值
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-13
      • 1970-01-01
      • 2014-05-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多