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*
一样
那么,问题是:这种额外的间接性有什么特别之处?也许关键是,只要有一级间接寻址,我们就可以使用virtualoperator=
来避免这种情况,而普通指针上没有相同的机制
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
指向的是-aBase
,因此为其指定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!
}