通过抽象基类的指针返回派生类对象

returning a derived class object through a pointer of its abstract base class

本文关键字:返回 派生 对象 指针 抽象 基类      更新时间:2023-10-16

我必须编写一个程序,其中一个函数将通过抽象基类返回派生类,因此当类返回到主程序时可以访问派生类的虚方法。

请记住,我不能更改主程序中的任何内容,因为我不是编写主程序的人。

#include<iostream>
using namespace std;
class A
{
private:
public:
    virtual void DoIt(void)=0;
    A(void){};
    ~A(void){};
};
class B:
        public A
{
private:
    int Num;
public:
    virtual void DoIt(void){Num=7;cout<<"its done";};
    B(void){};
    ~B(void){};
};
A& returnValue(void) 
{
        B item;
    return item;
}
void main()
{
    A& item=returnValue();
    item.DoIt();
}

当我尝试运行这个时,最后一行中断了构建,说DoIt是一个纯虚函数调用。什么好主意吗?

您正在返回对局部变量的引用,该引用在returnvalue中的调用完成时被销毁。不如试试下面的命令:

A &returnValue(void) {
    return *(new B);
}
int main() {
    A& item = returnValue();
    item.DoIt();
}

一个更好的解决方案是返回一个智能指针,让维护main函数的人对returnvalue返回的对象的生命周期负责:

#include <memory>
...
std::unique_ptr<A> returnValue(void) {
    return std::unique_ptr<A>(new B);
}
int main() {
    auto item = returnValue();
    item->DoIt();
}

returnValue()中返回的item在函数退出时被销毁。这个函数返回的是对销毁对象的引用。你需要以某种方式保存对象。例如:

A& returnValue(void) 
{
    B *item = new B();
    return *item;
}
void main()
{
    A& item=returnValue();
    item.DoIt();
    delete &item; // A's destructor must be virtual for this to work correctly
}

或:

B theItem;
A& returnValue(void) 
{
    return theItem;
}
void main()
{
    A& item=returnValue();
    item.DoIt();
}
在调用item.DoIt()的时候,虚表指针被修改(被B的析构函数修改)指向A的虚成员,而纯虚函数被存根为一个显示此错误的函数。但是,不能保证能够到达它,因为虚拟表指针已经驻留在释放的堆栈内存中。编译器可能已经将这些内存完全重用到其他地方了。

这里有几个问题。主要原因是returnValue中的item将立即超出作用域,因此在main中不再有可以调用DoIt的对象。因此,没有对象可以跟随继承树。这解释了您得到的错误(main中的item不再是B类型,因为它不存在)。然而,我很惊讶它没有崩溃。

要解决此问题,您需要复制对象(通过值而不是引用传递),或者将对象放在堆上(并传递指针)。对于后者:

A* returnValue(void) 
{
        B* item = new B;
    return item;
}
int main()
{
    A* item=returnValue();
    item->DoIt();
    delete item;
}

另一个问题是,你有虚函数,但没有虚析构函数!这可能是一个严重的问题。您将希望virtual ~A(void){};virtual ~B(void){};都是安全的。