[英]Why we can't implement polymorphism in C++ without base class pointer or reference?
首先看下面的代码(在此代码中,形状是基类,而线是派生类)
void drawshapes(shape sarray[],int size)
{
for(int i=0;i< size; i++)
sarray[i].draw();
}
main()
{
line larray[10];
larray[0]=line(p1,p2);//assuming that we have a point class
larray[1]=line(p2,p3);
..........
drawshapes(larray,10);
}
当我们编译该程序时,将首先调用shape的draw方法,然后程序终止。 为什么终止? 为什么没有基类指针或引用就不能实现多态性的技术原因是什么? 如果我们尝试使用对象数组实现多态,编译器将做什么? 请通过示例以易于理解的方式进行解释。 我将非常感谢。
首先:您要混合两个概念:多态性和值与参考语义。
多态性有多种形式。 根据您使用的运行时,其他选项可用。
一种解释型语言(例如Ruby,Python,Javascript等)允许进行“鸭子类型”操作:如果对象仅具有一个称为foo
的方法,则可以调用它。 通常,这些语言会进行垃圾回收,因此指针与对象的概念不太相关。
C ++有不同的观点:允许多态,但是以更严格的方式。 强制使用一个通用的基类(可能是抽象的)可以使编译器检查您的代码的语义:这样,编译器可以确保您确实是实现了预期接口的foo
方法,而不是foo
的杂乱混搭。
这种多态性是通过使用virtual
函数实现的: virtual
函数的指针在实现之间可能有所不同。 foo
的调用者首先必须查找函数指针的值,然后跳转到该函数。
到目前为止为多态。
现在进行遏制:如果在C ++中创建line
对象数组,则这些对象在内存中彼此相邻; 它们被价值所包含。 当您将数组传递给函数时,被调用的函数只能接收相同类型的数组。 否则,在数组中采用sizeof(shape)
的步骤,我们最终将在line
的中间。
为了解决这个问题,您可以“按引用”包含对象-在C ++中,我们为此使用了指针。
但是还有另一种实现多态功能的方法:模板。 您可以使用模板参数编写drawshapes
函数,该参数说明您使用的对象类型:
template< typename tShape, size_t N >
void drawshapes( tShape (&aShapes)[N] ) {
for( tShape* shape=aShapes; shape != aShapes+N; ++shape ) {
shape->draw();
}
}
(注意:有stl函数可以简化此操作,但这超出了问题的范围。
std::for_each( shapes, shapes+10, mem_fun_ref( &shape::draw ) );
)
您在问一个问题,并提供了一个失败的代码示例,但原因有所不同。 从您的问题的措辞:
为什么多态性需要引用/指针?
struct base {
virtual void f();
};
struct derived : public base {
virtual void f();
};
void call1( base b ) {
b.f(); // base::f
}
void call2( base &b ) {
b.f(); // derived::f
}
int main() {
derived d;
call1(d);
call2(d);
}
当您使用按值传递语义(或将派生的元素存储在基本容器中)时,您将创建derived
类型的元素的base
类型的副本。 这称为切片 ,因为它类似于以下事实:您有一个derived
对象,并且仅从该对象中切片/切割了base
子对象。 在示例中, call1
在main中的对象d
不起作用,而是使用base
类型的临时类型,并调用了base::f
。
在call2
方法中,您正在传递对base
对象的引用。 当编译器在main中看到call2(d)
,它将在d
创建对base
子对象的引用,并将其传递给函数。 该函数在类型为base
的引用上执行该操作,该引用指向derived
类型的对象,并将调用derived::f
。 同样的情况发生在指针上,当你得到一个base *
为derived
对象,该对象是否仍然derived
。
为什么我不能将derived
指针的容器传递给采用base
指针的容器的函数?
_Clearly如果derived
是base
,的容器derived
是的容器base
。
不。 derived
容器不是base
容器。 那会破坏类型系统。 使用的容器的最简单的例子derived
作为容器base
断裂的类型系统对象如下。
void f( std::vector<base*> & v )
{
v.push_back( new base );
v.push_back( new another_derived );
}
int main() {
std::vector<derived*> v;
f( v ); // error!!!
}
如果语言允许使用标记为错误的行,那么它将允许应用程序将非derived*
类型的元素插入容器,这将带来很多麻烦...
但是问题是关于值类型的容器...
当您具有值类型的容器时,元素将被复制到容器中。 将derived
类型的元素插入到base
类型的容器中,将在derived
对象内复制base
类型的子对象。 与上面的切片相同。 除了语言限制之外,还有一个很好的理由:当您拥有一个base
对象容器时,您就有空间仅容纳base
元素。 您不能将较大的对象存储到同一容器中。 否则,编译器甚至不知道为每个元素保留多少空间(如果以后再使用更大的类型扩展该怎么办?)。
在其他语言中,这似乎实际上是允许的(Java),但事实并非如此。 唯一的变化是语法。 当您在Java中使用String array[]
,实际上是在编写C ++中的string *array[]
。 所有非原始类型都是该语言中的引用,并且您没有在语法中添加*
的事实并不意味着容器包含String的实例 ,容器将引用包含在String中,与c ++指针比c ++更为相关参考。
您不能传递线阵列而不是形状阵列。 您必须使用指针数组。 发生这种情况的原因是,当函数尝试访问第二个成员时,它执行*(sarray + sizeof(shape))而不是*(sarray + sizeof(line)),这是访问数组第二个元素的正确方法行。
您想要这样的东西:
void drawshapes(shape *sarray[],int size)
{
for(int i=0;i< size; i++)
sarray[i]->draw();
}
main()
{
shape *larray[10];
larray[0] = new line(p1,p2);//assuming that we have a point class
larray[1] = new line(p2,p3);
..........
drawshapes(larray, 10);
// clean-up memory
...
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.