我正在学习C ++继承,所以我通过动态创建一个Base类来尝试这个代码,并对其Derived类进行了向下转换(显然它对转发无效),以便使这个动态创建的Base对象被指向派生类指针。但是当我通过这个Derived指针调用方法who()
时,它调用Derived类方法而不是Base类方法。
根据我的假设,没有为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
修改:: 我没有在上面的代码中释放内存,并且做了一个无效的向下翻转,因为我只是想看看会发生什么。所以只解释一下这种调用方法的行为。
答案 0 :(得分:2)
您的代码以这种方式运行的原因可能是因为编译器没有检查对象的实际类型(除非您的函数是virtual
,否则它不是必需的);它只是调用child::who
,因为你告诉它。也就是说,你的代码肯定是可疑的。
您是静态downcasting指向派生类指针的基类指针,而不是type-safe。 C ++不会阻止你这样做;您可以确保指针确实指向派生类型的对象。如果指针未引用派生类型的对象,则取消引用该指针可能是undefined behaviour。你很幸运,你的代码甚至可以打印任何东西。
您需要以确保您的基类函数who
为virtual
,否则函数调用将不会显示polymorphically。请注意,一旦将virtual
添加到您的代码中,您肯定会调用未定义的行为,因为您非法向下转换为无效类型。您可以使用dynamic_cast
安全地转发,如果对象不是指定类型,则会返回nullptr
。
您的基类通常也应该有一个virtual
析构函数,以便您的对象可以delete
多态化。说到这一点,您还需要确保在某个时刻delete
动态分配对象。通过手动调用new
和delete
,即使您知道需要调用delete
,也很容易泄漏这样的内存。已将std::unique_ptr
和std::shared_ptr
添加到C ++ 11中的标准库中以解决这些问题。除了最低级别的代码外,请使用这些代替new
和delete
。
总结一下,以下是我建议你的代码看起来的方式:
#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();
}
}
答案 1 :(得分:1)
多态性并非如此。有趣的是,从parent*
到child*
的C风格演员之所以有效,是因为这些类没有v-table或除who
之外的其他任何东西。因此,who
的地址必须与class
本身的地址相同。
parent *p = (parent*)new child();
会更有意义,但即便如此,如果你在p->who()
类中标记函数who
virtual
,parent
也只会调用子类函数尚未完成。
答案 2 :(得分:1)
首先,这不是有效的向下倾斜。 new parent()
的实际类型确实是parent
而不是child
。仅当真实(也称为动态)类型为child
但指向对象此时为parent
时才允许向下转换。
反过来会更有意义。如果您创建child
并将其分配给parent
指针,则可以。但即便如此:除非who
是virtual
,否则静态类型而不是动态类型决定调用哪个函数。
示例:
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();
然后,输出将是“我是孩子”,就像你期望的那样。
答案 3 :(得分:1)
您所看到的是工作中未定义的行为。
您的函数是非虚函数,它们只是您告诉指针指向编译器的类型的成员函数。
child *c = (child*)new parent;
这是一个c风格的演员阵容,强有力地使编译器相信指针c 肯定指向一个孩子的东西。
因此,当你调用c->who()
时,你专门调用child::who
,因为指针是指向孩子的指针。
没有发生任何可怕的事情,你会看到&#34;我是孩子&#34;是因为你没有尝试取消引用该指针或使用你的指向实例实际上没有的任何子特定字段。所以你逃脱了它。
答案 4 :(得分: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
指向c
,也会打印NULL
。因为不需要取消引用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
指针取消引用为(*this).x
。由于this
指向NULL
解除引用会导致Segmentation fault
。这显然是一种未定义的行为。
答案 5 :(得分:0)
很简单,在你调用c->who()
时,c的静态类型(即编译器对c知道的类型)是child*
而方法who
是非虚拟的。因此编译器会发出调用child::who
地址的指令。
编译器没有(通常,它怎么可能?)跟踪c
在运行时指向的对象的真实类型。
如果child
中的任何成员不在parent
中,则访问child::who
中的成员会产生越界访问,这可能会导致SIGSEGV,或其他不愉快。
最后,关于您的观察行为是否得到标准保证,我倾向于同意@ P45Imminent:parent
和child
都满足POD的要求而child
没有非静态数据成员。因此,parent
和child
对象的运行时布局要求根据标准无法区分(至少就parent
和child
的方法而言 - 或许{ {1}}和child
最后可能有不同的填充量?)。所以其中一条评论中引用的9.3.1 / 2的行不适用于恕我直言。如果标准不支持布局上的这种假设,我很乐意听到更多知识渊博的人。