2016-03-17 188 views
1

我正在学习C++继承,因此我通过动态创建基类来尝试此代码,并对其派生类进行了向下转换(显然,它对于向下转换无效)以便使这个动态创建的Base对象由Derived类指针指向。但是当我通过这个Derived指针调用方法who()时,它会调用Derived类方法而不是Base类方法。C++继承:派生类指向基类调用Derived类方法

根据我的假设,没有为Derived类创建对象,那么如何调用非创建的Derived类对象的方法而不是实际创建的Base类对象?

我为这种现象搜索了一下,但是找不到一个清晰脆弱的解释来调用非创建的Derived类对象的方法。如果它是根据标准,然后解释我是如何工作的。 我知道,如果我制作方法virtual,但这里使用的方法不是virtual,故事会有所不同。

class parent{ 
    public: 
     void who(){ 
      cout << "I am parent"; 
     } 
}; 

class child : public parent{ 
    public: 
     void who(){ 
      cout << "I am child"; 
     } 
}; 

int main(){ 
    child *c = (child *)new parent(); 
    c->who(); 
    return 0; 
} 

输出是I am child但我预计I am parent

编辑:: 我没有在上面的代码中解脱出来了内存并提出了无效沮丧的,因为我只是想看看会发生什么。所以只解释调用方法的这种行为。

+2

行为未定义。任何事情都可能发生。即使你让这个函数变成虚拟的,它也是一样的未定义的故事。 – molbdnilo

+0

@molbdnilo:我不认为它在这个特殊情况下是未定义的。看到我非常好的答案。 –

+2

@ P45Imminent - 行为未定义。事实上,你可以对观察到的行为进行理性解释并不会改变这一点。 “未定义的行为”意味着语言定义不会告诉您发生了什么;而已。 –

回答

0

由于@Mark Ransom在评论中曾建议,我看了后When does invoking a member function on a null instance result in undefined behavior?。然后我想出了我自己的解决方案。

调用非创建对象方法的这种行为与类parent类或child类或向下或继承无关。我静静地告诉通过向下调用child::who(),编译器调用child::who()而不关心对象类型。

但是如何调用who()方法,因为没有为child类创建对象?

其中我试图调用没有任何成员变量,所以没有必要提领this指针的方法。它只是调用who(),如果child类指针也指向NULL,则同样的行为也是如此。

child *c = NULL; 
c->who(); 

它打印I am child即使cNULL。因为不需要取消指针this。让我们有一种情况,who()方法包含如下的成员变量x

class child : public parent{ 
    private: 
     int x; 
    public: 
     void who(){ 
      x = 10; 
      cout << "I am child " << x; 
     } 
}; 

child *c = NULL; 
c->who() 

现在,它会导致Segmentation fault因为为了使用x编译器必须取消引用this指针x作为(*this).x。由于this指向NULL解引用将导致Segmentation fault。这显然是一个未定义的行为。

+0

其实它们都是未定义的行为,只是在一个“未定义”的案例中竟然是无害的。我无法强调你应该总是避免未定义的行为,即使它看起来有效,因为它可能会在没有明显原因的情况下在将来停止工作。 –

1

多态性不能像那样工作。有趣的是,你的C风格从parent*转换为child*可以工作,因为这些类没有v-表或其他功能who。所以who的地址必须与class本身的地址相同。

parent *p = (parent*)new child();

会更有意义,但即使是这样,如果你在parent类,你没有做标记功能whovirtualp->who()只会调用子类的功能。

+0

即使它是无意义的,但是如何调用子方法,因为没有为子类创建对象 –

+0

@jblixr你可能想了解v-表以及如何在内存中布局对象以理解为什么会发生这样的结果。 – CompuChip

+0

@jvlixr因为sizeof(父级)必须非零(标准坚持),但sizeof(child)+ sizeof(parent)= sizeof(parent)不重要。 –

1

首先,这是无效的downcast。 new parent()的实际类型确实是parent而不是child。只有当真实(也称为动态)类型为child,但指向对象此刻为parent时,才允许向下转换。

反过来会更有意义。如果你创建一个child并将它分配给一个parent指针,这可以。但即使如此:除非whovirtual静态类型而不是动态类型决定调用哪个函数。

例子:

class parent{ 
    public: 
     void who(){ 
      cout << "I am parent"; 
     } 

    ~virtual parent() {}; // important to have that in class hierarchies where you might use a child that is assigned to a parent pointer! 
}; 

class child : public parent{ 
    public: 
     void who(){ 
      cout << "I am child"; 
     } 
}; 

int main(){ 
    parent *c = (parent *)new child(); 
    c->who(); // output would still be parent because who() is not virtual 
    delete c; // important! never forget delete! 
    return 0; 
} 

如果您使用,而另一方面,

virtual void who(); 

然后,输出将是 “我的孩子” 像你期望的那样。

+0

但是如何调用子方法?因为没有任何对象为孩子创建任何地方 –

+0

它的工作原理是因为这两个类都没有一个vtable,并且只有一个函数;该函数的地址必须与该类的地址相同。 –

+0

由于您的代码被盗用,因此无法保证任何内容。但是静态类型决定在这种情况下,最常见的错误代码。有时甚至有可能写'child * c = 0x12345678; c-> who();'你会得到输出,因为给出了静态类型(child)。当然,这种代码绝对不能使用。 – IceFire

2

您的代码以这种方式行为的原因可能是因为编译器不检查对象的实际类型(除非您的函数是virtual)否则不需要;它只是叫child::who,因为你告诉它。也就是说,你的代码肯定是可疑的。

你是静态的downcasting指向派生类指针的基类指针,它不是type-safe。 C++不会阻止你这样做;这取决于你确保你的指针真的指向派生类型的一个对象。如果指针不引用派生类型的对象,则解引用指针可能是undefined behaviour。你很幸运,你的代码甚至可以打印任何东西。

需要,以确保您的基类功能whovirtual,否则函数调用将不行为polymorphically。请记住,一旦您将virtual添加到您的代码中,您肯定会调用未定义的行为,因为您非法向下转换为无效类型。如果对象不是指定的类型,则可以使用dynamic_cast进行安全降级,如果对象不是指定的类型,则将返回nullptr

在基类中,通常还应该有一个virtual析构函数,以便您的对象可以是多态的delete d。说到这一点,您还需要确保在某个时刻您的动态分配对象delete。通过手动呼叫newdelete,即使您知道需要拨打delete,也很容易泄漏这样的内存。在C++ 11中将std::unique_ptrstd::shared_ptr添加到标准库中以解决这些问题。除了最底层的代码外,请使用这些代替newdelete

总之,这里是我建议你的代码应该是:

#include <iostream> 
#include <memory> 

class parent { 
    public: 
     virtual ~parent() {} 

     virtual void who() { 
      std::cout << "I am parent"; 
     } 
}; 

class child : public parent { 
    public: 
     void who() override { 
      std::cout << "I am child"; 
     } 
}; 

int main() { 
    auto p = std::make_unique<parent>(); 
    auto c = dynamic_cast<child*>(p.get()); 

    if (c) // will obviously test false in this case 
    { 
     c->who(); 
    } 
} 
+0

我会按照你的建议,但一个子对象甚至没有创建,那么它如何调用子方法? –

+0

@jblixr因为,正如我所提到的(我编辑过;抱歉,如果你没有看到),编译器不需要检查对象的实际类型。正如你所提到的那样,添加'virtual'使得它的行为符合你的期望,但是添加'virtual'肯定会使你的代码变得非法,因为正如我之前提到的,你不能通过对派生类型的引用来使用基类型的对象!但是,您可以做相反的处理(通过基地获取访问权限)。 –

+0

@jblixr为了清晰起见,再次编辑。 –

1

你看到的是未定义的行为在工作。

你的函数是非虚拟的,它们只是指向编译器指针指向类型的成员函数。

child *c = (child*)new parent; 

这是一个C样式转换是强大的武器编译成的信念,指针Ç绝对点的东西,是一个孩子。

因此,当您拨打c->who()时,您特别呼叫child::who,因为指针是指向儿童的指针。

没有可怕的事情发生的原因,你看到“我是孩子”是因为你不试图取消引用该指针或使用任何特定于子对象的字段,您的指向实例并不实际有。所以你逃避了。

+0

你是绝对正确的,并学会了自己http://stackoverflow.com/a/36076748/5756174 –

0

很简单,在该调用c->who()点,静态型μc(即编译器知道对于C的类型)是child*并且该方法who是非虚拟的。因此,编译器发出拨打child::who地址的指令。

编译器没有(一般情况下它怎么可能?)跟踪在运行时指向c的对象的真实类型。

如果您在child::who曾在child不在parent任何成员,访问这些成员将产生一个彻头彻尾的越界访问,这可能会导致一个SIGSEGV,或其他不愉快。

最后,您观察到的行为是否是由标准的保证,我倾向于@ P45Imminent同意:无论parentchild满足要求POD和child没有非静态数据成员。因此,要求每个标准难以区分parentchild对象的运行时间布局(至少就parentchild而言,可能childparent最后可能会有不同的填充量)。所以从其中一个评论中引用的9.3.1/2的行不适用于恕我直言。如果这种布局假设不被标准支持,我很乐意听到更多的人了解。

相关问题