通过基类指针访问子类成员函数

3
我正在尝试编写一个类,使用DOM将注册列表作为XML写入文件。注册列表包含三种类型的注册:基本注册类Registration,派生自Registration的GuestRegistration和StudentRegistration。
GuestRegistration类具有独特成员category,而student registration具有独特成员qualification。
当我遍历注册指针列表时,我只能访问基类Registration的成员函数。是否有办法访问子类的数据成员以使用getter函数getCategory和getQualification?我尝试创建GuestRegistration和StudentRegistration指针,但出现指针转换错误。
void RegistrationListWriter::write(RegistrationList r) {
    QList<Registration*> regList = r.getRegistrationList();

    for (int i = 0; i < regList.size(); ++i) {
        QString cn = regList.at(i)->metaObject()->className();
        Person tempPerson = regList.at(i)->getAttendee();

        appendRegistrationAndType(cn);
        appendAttendee(tempPerson);
        //this is where my issue starts
        if (cn == "GuestRegistration") {
            GuestRegistration guestReg = regList.at(i);
            appendAttendeeCatagory(guestReg.getCatagory());
        }

        if (cn == "StudentRegistration") {
            StudentRegistration* stuReg = regList.at(i);
            appendAttendeeQualification(stuReg->getQualification());
        }

        appendBookingDate(regList.at(i)->getBookingDate().toString());
        appendRegistrationFee(regList.at(i)->calculateFee());

    }
}

1
你应该能够对两种指针类型进行dynamic_cast,如果返回的不是nullptr,那么你将可以访问子类方法。 - Mark Ransom
4个回答

5

您可以使用dynamic_cast检查特定的子类:

void RegistrationListWriter::write(RegistrationList r) {
    QList<Registration*> regList = r.getRegistrationList();

    for (int i = 0; i < regList.size(); ++i) {
        Registration *reg = regList.at(i);

        appendRegistrationAndType(reg->metaObject()->className());
        appendAttendee(reg->getAttendee());

        if (GuestRegistration *guestReg = dynamic_cast<GuestRegistration*>(reg)) {
            appendAttendeeCatagory(guestReg->getCatagory());
        }
        else
        if (StudentRegistration* stuReg = dynamic_cast<StudentRegistration*>(reg)) {
            appendAttendeeQualification(stuReg->getQualification());
        }
        // and so on ...

        appendBookingDate(reg->getBookingDate().toString());
        appendRegistrationFee(reg->calculateFee());    
    }
}

然而,我建议在Registration类本身中实现一个虚拟方法,让您的子类可以覆盖该方法以根据需要注册其他项,例如:

class Registration {
    ...
    virtual void appendExtraAttendees(RegistrationListWriter *writer){}
    ...
};

class GuestRegistration : public Registration {
    ...
    virtual void appendExtraAttendees(RegistrationListWriter *writer);
    ...
};

void GuestRegistration::appendExtraAttendees(RegistrationListWriter *writer){
    writer->appendAttendeeCatagory(getCatagory());
}

class StudentRegistration : public Registration {
    ...
    virtual void appendExtraAttendees(RegistrationListWriter *writer);
    ...
};

void StudentRegistration::appendExtraAttendees(RegistrationListWriter *writer){
    writer->appendAttendeeQualification(getQualification());
}

void RegistrationListWriter::write(RegistrationList r) {
    QList<Registration*> regList = r.getRegistrationList();

    for (int i = 0; i < regList.size(); ++i) {
        Registration *reg = regList.at(i);

        appendRegistrationAndType(reg->metaObject()->className());
        appendAttendee(reg->getAttendee());

        reg->appendExtraAttendees(this);

        appendBookingDate(reg->getBookingDate().toString());
        appendRegistrationFee(reg->calculateFee());    
    }
}

或者:

class Registration {
    ...
    virtual void appendAttendees(RegistrationListWriter *writer);
    ...
};

void Registration::appendAttendees(RegistrationListWriter *writer){
    writer->appendAttendee(getAttendee());
}

class GuestRegistration : public Registration {
    ...
    virtual void appendAttendees(RegistrationListWriter *writer);
    ...
};

void GuestRegistration::appendAttendees(RegistrationListWriter *writer){
    Registration::appendAttendees(writer);
    writer->appendAttendeeCatagory(getCatagory());
}

class StudentRegistration : public Registration {
    ...
    virtual void appendAttendees(RegistrationListWriter *writer);
    ...
};

void StudentRegistration::appendAttendees(RegistrationListWriter *writer){
    Registration::appendAttendees(writer);
    writer->appendAttendeeQualification(getQualification());
}

void RegistrationListWriter::write(RegistrationList r) {
    QList<Registration*> regList = r.getRegistrationList();

    for (int i = 0; i < regList.size(); ++i) {
        Registration *reg = regList.at(i);

        appendRegistrationAndType(reg->metaObject()->className());

        reg->appendAttendees(this);

        appendBookingDate(reg->getBookingDate().toString());
        appendRegistrationFee(reg->calculateFee());    
    }
}

非常感谢您提供的详细答案@Remy Lebeau。为什么创建虚函数会比使用dynamic_cast或QObject_cast更好呢? - user2094257
dynamic_cast 在运行时需要执行 RTTI 查找以确定源对象是否与请求的类相关,并返回适当的指针,因此具有开销。此外,如果将来添加更多派生类,则必须添加更多的 dynamic_cast 检查。使用虚方法调用可以避免这种情况。调用虚方法很简单,只需在对象的虚表中进行简单的索引,因此调用可以直接跳转到运行时最派生的实现。更加高效,调用代码不必关心派生类的细节。 - Remy Lebeau

2

直接的 C++ 工具是 dynamic_cast<>()。

一般来说,最好不要最初设计需要这种转换的项目。可以考虑各种设计模式。

我看到您正在使用 metaObject(),这意味着 RegistrationQObject 作为基类。在这种情况下,可以使用qobject_cast:

qobject_cast() 函数的行为类似于标准 C++ dynamic_cast(),它的优点是它不需要 RTTI 支持,并且它可以跨动态库边界工作。


当我使用 QObject_cast 时,会出现以下错误:C:\ Qt \ Qt5.3.0 \ 5.3 \ mingw482_32 \ include \ QtCore \ qobject.h:-1:在复制构造函数'RegistrationList :: RegistrationList(const RegistrationList&)'中: C:\ Qt \ Qt5.3.0 \ 5.3 \ mingw482_32 \ include \ QtCore \ qobject.h:465:错误:'QObject :: QObject(const QObject&)'是私有的 Q_DISABLbE_COPY(QObject) - user2094257
@user2094257 你不能将基类实例转换为派生类实例。只能转换指针:qobject_cast<GuestRegistration*>(base_ptr),其中 base_ptr 是指向基类实例 (Registration*) 的指针。 - Orest Hera

1

不要使用dynamic_cast,你可以让基类提供一个接口,派生类使用该接口来编写其特定于类的数据。


0

可能你需要将这些方法变成虚方法。 非虚方法使用编译时所用类的方法,而子类中的虚方法会在运行时被选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接