C++ 将派生**转换为基本**并将派生*转换为基本***

C++ 将派生**转换为基本**并将派生*转换为基本***,c++,pointers,inheritance,C++,Pointers,Inheritance,好的,我一直在阅读关于将派生**转换为基**的问题,以及为什么它是被禁止的,我得到的问题是,你可以分配给基*一些不是派生*的东西,所以我们禁止这样做 到目前为止,一切顺利 但是,如果我们深入应用这一原则,为什么我们不禁止这样的例子呢 void nasty_function(Base *b) { *b = Base(3); // Ouch! } int main(int argc, char **argv) { Derived *d = new Derived; nasty_func

好的,我一直在阅读关于将
派生**
转换为
基**
的问题,以及为什么它是被禁止的,我得到的问题是,你可以分配给
基*
一些不是
派生*
的东西,所以我们禁止这样做

到目前为止,一切顺利

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

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?
}
我同意,
讨厌的_函数
做了一些愚蠢的事情,因此我们可以说允许这种转换是好的,因为我们启用了有趣的设计,但我们也可以说,对于双重间接:你得到了一个
基**
,但是你不应该给它的尊重分配任何东西,因为你真的不知道
Base**
从哪里来,就像
Base*
一样

那么,问题是:这种额外的间接性有什么特别之处?也许关键是,只要有一级间接寻址,我们就可以使用virtual
operator=
来避免这种情况,而普通指针上没有相同的机制

nasty_function(d); // Ooops, now *d points to a Base. What would happen now?
不,没有。它指向一个
派生的
。该函数只需更改现有
派生的
对象中的
子对象。考虑:

#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);
}
#包括
结构基{
基(int x):x(x){}
int x;
};
派生结构:基{
派生(intx,inty):基(x),y(y){
int-y;
};
int main(int argc,字符**argv)
{
派生d(1,2);//说真的,WTF是人和新的吗?
//使用指针不需要新的指针
//快停下来
断言(d.x==1);
断言(d.y==2);
函数&d;
断言(d.x==3);
断言(d.y==2);
}
d
不会神奇地变成
Base
,是吗?它仍然是
派生的
,但它的
基础
部分发生了变化


图片中:)

这就是
Base
Derived
对象的外观:

当我们有两个级别的间接寻址时,它不起作用,因为分配的对象是指针:

请注意,所讨论的
Base
派生的
对象都没有试图更改:只有中间的指针被更改

但是,当您只有一级间接寻址时,代码会以对象允许的方式修改对象本身(它可以通过设置私有、隐藏或从
基中删除赋值运算符来禁止它):

注意这里没有指针是如何改变的。这就像任何其他改变对象一部分的操作一样,比如
d.y=42

不,
讨厌的函数()
并不像听起来那么讨厌。由于指针
b
指向的是-a
Base
,因此为其指定
Base
-值是完全合法的

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

因此,我认为,您的示例与指向指针的指针大小写没有太大关系。

*b=Base(3)
调用
Base::operator=(const Base&)
,它实际上存在于
派生的
中,因为成员函数(inc.operators)是继承的

然后会发生的事情(调用
派生::操作符=(const Base&)
)有时被称为,是的,这是不好的(通常)。C++中“类变”操作符(<代码>=/COD>)的全然存在是一个令人悲伤的结果。

(注意到在java、C++、python等大多数OO语言中不存在“类”操作符;在对象上下文中,代码>> /Cord>表示引用赋值,这类似于C++中的指针赋值;


总结:

强制转换
派生**
->
基**
是禁止的,因为它们会导致类型错误,因为这样您可能会得到一个
派生*
类型的指针,指向
类型的对象

您提到的问题不是类型错误;这是一种不同类型的错误:错误地使用了
派生的
对象的接口
,其根源是该对象继承了其父类的“变得像”操作符,这是一个令人遗憾的事实



(是的,我故意在对象上下文中称op=为“变得像”,因为我觉得“赋值”不是一个好名字来说明这里发生了什么。)

您给出的代码很有意义。实际上,赋值运算符不能覆盖特定于派生的数据,而只能覆盖基。虚函数仍然是派生函数,而不是基函数

*b = Base(3); // Ouch!
这里
*b
的对象实际上是
b
,它是
*d
的基本子对象。只有基本子对象被修改,派生对象的其余部分不会更改,
d
仍然指向派生类型的相同对象

您可能不想允许修改基,但就类型系统而言,它是正确的。
派生的
是一个
基础

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

允许您询问的转换与此没有区别:

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

通过阅读我的问题的好答案,我想我找到了问题的要点,它来自OO的第一原则,与子对象和运算符重载无关

关键是,只要重新创建
,就可以使用
派生的
void f(Base **b)
void f(Base **b)
{
  Base *pb = *b;
  ...
}
void f(Base *b)
void f(Base *b)
{
  Base pb = *b; // *b is a Derived? No problem!
}