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

时间:2010-02-03 07:59:31

标签: c++ oop compiler-theory



首先看一下下面的代码(在这个代码形状中是基类,而行是派生类)

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方法,然后程序终止。为什么它会终止?为什么我们不能在没有基类指针或引用的情况下实现多态性这是什么技术原因?如果我们试图用对象数组实现多态,那么编译器会做什么?请用例子以可理解的方式解释。我将非常感激。

4 个答案:

答案 0 :(得分:5)

首先:你混合了两个概念:多态,价值与参考语义。

运行时多态性

多态性有多种形式。根据您使用的运行时间,可以使用其他选项。

解释性语言(如Ruby,Python,Javascript,...)允许“鸭子打字”:如果一个对象只有一个名为foo的方法,你可以调用它。通常这些语言进行垃圾收集,因此指针与对象的概念不太相关。

C ++有不同的观点:允许多态,但是更严格。实现一个公共基类(可能是抽象的)允许编译器检查代码的语义:这样编译器确保你真正意味着实现预期接口的foo方法,而不是foo mishmash { {1}}秒。

这种多态性是通过使用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 ) );

答案 1 :(得分:5)

您正在提出一个问题并提供一个代码示例,但由于其他原因而失败。从问题的措辞:

为什么多态性需要引用/指针?

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);
}

当您使用按值传递语义(或在基本容器中存储派生元素)时,您将创建类型base的元素类型derived的副本。这称为切片,因为它类似于您拥有derived对象并且切片/切割只有base子对象的事实。在示例中,call1无法使用main中的对象d,而是使用base类型的临时对象,并调用base::f

call2方法中,您传递对base对象的引用。当编译器在main中看到call2(d)时,它将在base中创建对d子对象的引用,并将其传递给函数。该函数对base类型的引用执行操作,该引用指向derived类型的对象,并将调用derived::f。指针也是如此,当你在base *对象中得到derived时,对象仍为derived

为什么我不能将derived指针的容器传递给一个容量为base指针的函数?

_如果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类型的容器中将在base对象中复制derived类型的子对象。这与上面的切片相同。除了这是一个语言限制之外,它有充分的理由,当你有一个base个对象的容器时,你有空间只容纳base个元素。您不能将较大的对象存储到同一容器中。否则,编译器甚至不知道为每个元素保留多少空间(如果我们稍后使用更大的类型扩展呢?)。

在其他语言中,它似乎实际上是允许的(Java),但事实并非如此。唯一的变化是语法。当你在Java中有String array[]时,你实际上在C ++中编写了string *array[]的等价物。所有非基本类型都是语言中的引用,并且您不在语法中添加*这一事实并不意味着容器包含 String 的实例,容器包含引用到字符串中,与c ++引用更相关,而不是c ++引用。

答案 2 :(得分:3)

您不能传递一行数而不是一个形状数组。 您必须使用指针数组。 这是因为当函数尝试访问第二个成员时,它会执行*(sarray + sizeof(shape))而不是*(sarray + sizeof(line)),这将是访问数组的第二个元素的正确方法线。

答案 3 :(得分:1)

你想要这样的东西:

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
   ...
}