繁体   English   中英

为什么没有基类指针或引用我们就不能在C ++中实现多态?

[英]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如果derivedbase ,的容器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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM