将派生**转换为基础**并将派生*转换为基础*

时间:2012-06-29 15:26:22

标签: c++ pointers inheritance

好的,我正在阅读this entry in the FQA处理有关将Derived**转换为Base**以及为何被禁止的问题,我得知问题是您可以分配Base*某个不是Derived*的东西,所以我们禁止这样做。

到目前为止,非常好。

但是,如果我们深入应用这一原则,为什么我们不禁止这样的例子呢?

void nasty_function(Base *b)
{
  *b = Base(3); // Ouch!
}

int main(int argc, char **argv)
{
  Derived *d = new Derived;
  nasty_function(d); // Ooops, now *d points to a Base. What would happen now?
}

我同意nasty_function做一些愚蠢的事情,所以我们可以说让这种转换很好,因为我们启用了有趣的设计,但我们可以说这也是为了双重间接:你有一个{{ 1}},但你不应该为它的顺序分配任何东西,因为你真的不知道Base **的来源,就像Base **一样。

所以,问题是:间接的额外级别有什么特别之处?也许重点是,只有一个间接层,我们可以使用虚拟Base *来避免这种情况,而普通指针上没有相同的机制吗?

6 个答案:

答案 0 :(得分:14)

nasty_function(d); // Ooops, now *d points to a Base. What would happen now?

不,它没有。它指向Derived。该函数只是更改了现有Base对象中的Derived子对象。考虑:

#include <cassert>

struct Base {
    Base(int x) : x(x) {}
    int x;
};
struct Derived : Base {
     Derived(int x, int y) : Base(x), y(y) {}
     int y;
};

int main(int argc, char **argv)
{
  Derived d(1,2); // seriously, WTF is it with people and new?
                  // You don't need new to use pointers
                  // Stop it already
  assert(d.x == 1);
  assert(d.y == 2);
  nasty_function(&d);
  assert(d.x == 3);
  assert(d.y == 2);
}

d不会神奇地成为Base,是吗?它仍然是Derived,但它的Base部分发生了变化。


在图片中:))

这是BaseDerived个对象的样子:

Layouts

当我们有两个间接层时,它不起作用,因为被分配的东西是指针:

Assigning pointers - type mismatch

请注意,未尝试更改有问题的BaseDerived对象:只有中间指针是。

但是,当你只有一个间接层时,代码以对象允许的方式修改对象本身(它可以通过私有,隐藏或删除{{1}中的赋值运算符来禁止它。 }):

Assigning with only one level of indirection

注意这里没有改变指针。这就像改变对象的一部分的任何其他操作一样,如Base

答案 1 :(得分:7)

不,nasty_function()并不像听起来那么令人讨厌。由于指针b指向是-a Base的内容,因此为其分配Base - 值是完全合法的。

注意:您的“Ooops”评论不正确:d仍然指向与通话前相同的Derived!只是,它的Base部分被重新分配(按值!)。如果这导致整个Derived不一致,则需要通过使Base::operator=()虚拟来重新设计。然后,在nasty_function()中,实际上将调用Derived赋值运算符(如果已定义)。

所以,我认为,你的例子与指针到指针的情况没那么多。

答案 2 :(得分:2)

*b = Base(3)调用Base::operator=(const Base&),它实际存在于Derived中,因为成员函数(包含运算符)是继承的。

然后会发生什么(调用Derived::operator=(const Base&))有时会被称为 "slicing" ,是的,它很糟糕(通常)。这是C ++中“变成类似”运算符(=)纯粹无所不在的悲惨结果。

(请注意,大多数OO语言中都不存在“变形”运算符,如Java,C#或Python;对象上下文中的=表示引用赋值,类似于C ++中的指针赋值;)


总结:

投射Derived** - &gt; Base**被禁止,因为它们可能会导致类型错误,因为这样您最终会得到一个类型为Derived*的指针指向Base类型的对象

您提到的问题不是类型错误;这是一种不同类型的错误:错误使用derived对象的接口,这源于它继承了其父类的“变形”运算符的遗憾事实。


(是的,我故意将对象上下文中的op =称为“变得像”,因为我觉得“赋值”并不是一个好名字来展示这里发生的事情。)

答案 3 :(得分:0)

你提供的代码是有道理的。实际上,赋值运算符不能覆盖特定于Derived的数据,而只能覆盖基数。虚函数仍来自Derived而不是Base。

答案 4 :(得分:0)

*b = Base(3); // Ouch!

此处*b处的对象确实是B,它是*d的基础子对象。只有那个基础子对象被修改,派生对象的其余部分才不会改变,d仍然指向派生类型的同一个对象。

您可能不希望允许修改基础,但就类型系统而言,它是正确的。 Derived Base

对于非法指针案例,情况并非如此。 Derived*可转换为Base*但不是同一类型。它违反了类型系统。

允许您提出的转换与此无异:

Derived* d;
Base b;
d = &b;
d->x;

答案 5 :(得分:0)

通过我的问题的好答案,我认为我得到了问题的重点,它来自OO中的第一原则,与子对象和运算符重载无关。

关键是,只要需要Derived(替换原则),您就可以使用Base,但只要{{>> ,就<{1}}由于可以为其分配派生类实例的指针,因此需要{1}}。

使用此原型获取功能:

Derived*

Base*可以使用void f(Base **b) 执行大量操作,并将其解除引用:

f

如果我们转到b void f(Base **b) { Base *pb = *b; ... } ,则表示我们使用f作为Derived**,这是不正确的,因为我们可能会指定{{1} } Derived*但不是Base*

另一方面,请使用此功能:

OtherDerived*

如果Base*取消引用Derived*,那么我们会使用void f(Base *b) 代替f,这完全没问题(前提是您正确实施了类层次结构):

b

用另一种方式说:替换原则(使用派生类而不是基类)适用于实例,而不是指针,因为“概念“指向Derived的指针”指向继承Base的任何类的实例“,继承void f(Base *b) { Base pb = *b; // *b is a Derived? No problem! } 的类集严格包含继承{{1}的类集合}}