从基类指针调用派生类的非虚成员函数

3
我们知道,在C++中,如果派生类成员函数是虚函数,那么可以通过基类指针访问这些成员函数。那么有没有一种方法可以通过基类指针访问派生类成员函数,即使这些成员函数不是虚函数或纯虚函数呢?
换句话说,我想通过基类指针调用仅存在于派生类而不在基类中的成员函数。我该如何实现这个目标?
例如,如果我设计一个工厂模式,
class Vehicle {
public:
    virtual void printVehicle() = 0;
    static Vehicle* Create(VehicleType type);
};
class TwoWheeler : public Vehicle {
public:
    void printVehicle() {
        cout << "I am two wheeler" << endl;
    }
    void Some2WheelerONLYSpecificOPeration()
    {

    }
};
class ThreeWheeler : public Vehicle {
public:
    void printVehicle() {
        cout << "I am three wheeler" << endl;
    }
void Some3WheelerONLYSpecificOPeration()
    {

    }
};
class FourWheeler : public Vehicle {
    public:
    void printVehicle() {
        cout << "I am four wheeler" << endl;
    }
void Some4WheelerONLYSpecificOPeration()
    {

    }
};

// Factory method to create objects of different types.
// Change is required only in this function to create a new object type
Vehicle* Vehicle::Create(VehicleType type) {
    if (type == VT_TwoWheeler)
        return new TwoWheeler();
    else if (type == VT_ThreeWheeler)
        return new ThreeWheeler();
    else if (type == VT_FourWheeler)
        return new FourWheeler();
    else return NULL;
}

int main()
{
Vehicle* basePtr =   Vehicle::Create(VT_TwoWheeler);
basePtr->Some2WheelerONLYSpecificOPeration();   //HOW TO ACHIEVE THIS CALL

basePtr =   Vehicle::Create(VT_ThreeWheeler);
basePtr->Some3WheelerONLYSpecificOPeration(); //HOW TO ACHIEVE THIS CALL

basePtr =   Vehicle::Create(VT_FourWheeler);
basePtr->Some4WheelerONLYSpecificOPeration(); // //HOW TO ACHIEVE THIS CALL
}

6
dynamic_cast 或访问者模式。 - Jarod42
3
如果基类中没有它们,那么您必须显式地将其转换为派生类。 - Oliver Charlesworth
9
虽然之前的两个回答告诉您如何完成这样的操作,但在大多数情况下,我认为真正的答案是:不要这样做!这显示了未能正确地分离实现和接口。考虑更改设计:要么扩展基类的接口,要么完全采用其他方法(如果不了解上下文,则很难确定)。仅派生出接口是可以的,但您不应该需要从通过基类进行交互的代码中访问它。 - Nir Friedman
2
@codeLover,您能否更详细地说明您的用例呢?您想要做什么表明您存在严重的设计缺陷。 - user0042
2
你可以这样做,但只能在被解雇之前做一次。 - n. m.
显示剩余6条评论
3个回答

2
我想通过基类指针调用仅存在于派生类而不在基类中的成员函数。我该如何实现?
你不能使用指向基类的指针调用派生类的非虚成员函数。
你需要一个指向派生类的指针。最简单的方法是使用dynamic_cast获取指向派生类的指针,检查转换是否成功,然后使用指向派生类的指针调用派生类成员函数。
更好的方法是在基类中提供虚成员函数,并在派生类中实现。

请问您能否举个例子,我该如何在这里使用 dynamic_cast? - codeLover
2
@codeLover 那很可能不是你想要做的事情。你能详细说明一下你想要实现什么吗?如果只是为了避免在基类中提供定义,最好使用纯虚函数。然而,使用dynamic_cast相当简单:http://en.cppreference.com/w/cpp/language/dynamic_cast - user0042
此外,dynamic_cast需要运行时信息才能正常工作。因此,基类需要有一个虚成员函数以便dynamic_cast能够使用。它可以只是一个虚析构函数。 - Hari

1
你可以使用 dynamic_cast 做任何想做的事情,但这会在代码审查时导致令人失望的结果。相反,我建议你采用与 printVehicle 相同的方法。
class Vehicle
{
public:
    // without a virtual destructor you are walking into
    // a very bad bug. The wrong destructor may be called.
    virtual ~Vehicle()
    {
    } 
    virtual void printVehicle() = 0;

    // Specific stuff that all children must provide
    virtual void doTypeSpecificStuff() = 0;

    // this is actually a bit of a ideological weird. I'm not sure I can call
    // it a flaw. By making this factory function a member of Vehicle, Vehicle
    // must now know its children. If this is the case, the VehicleType enum
    // should probably be a member of Vehicle, but personally I think this
    // factory should be a totally free function.
    static Vehicle* Create(VehicleType type);
};
class TwoWheeler: public Vehicle
{
public:
    void printVehicle()
    {
        cout << "I am two wheeler" << endl;
    }
    void doTypeSpecificStuff()
    {
        cout << "Doing two wheeler stuff" << endl;
    }
};

略去另外两个类和 Vehicle::Create 以节省空间。
int main()
{
    Vehicle* basePtr = Vehicle::Create(VT_TwoWheeler);
    basePtr->doTypeSpecificStuff(); //HOW TO ACHIEVE THIS CALL
    // leaking memory here, so
    delete basePtr;
    // but also look into std::unique_ptr. Much better suited to this behaviour


}

实际上,让我们立即行动起来,关于 std::unique_ptr 的最后一条评论。 unique_ptr 为您管理动态分配,因此您不必在代码中混杂 delete 并冒着错过一个或太早删除的风险。只要 unique_ptr 在作用域内,指针就有效。如果您可以编译,则指针是好的,除非您做了傻事,例如从未将其指向任何内容或手动删除指针。

既然我们已经在这里了,让我们消除我之前对 vehicle::Create 的担忧。

首先,我们定义一个自由函数来替换 Create 并返回一个 unique_ptr。由于我讨厌在我的代码中到处都要有 if (ptr != NULL) 检查以确保对象确实被创建,所以当我们无法将提供的车辆类型与类匹配时,让我们通过抛出异常来大惊小怪。

而且,我们将使用一个更加优雅的 switch 语句,而不是一连串的 if-else if

std::unique_ptr<Vehicle> SmarterVehicleFactory(VehicleType type)
{
    switch (type)
    {
        case  VT_TwoWheeler:
            return std::make_unique<TwoWheeler>();
        case  VT_ThreeWheeler:
            return std::make_unique<ThreeWheeler>();
        case  VT_FourWheeler:
            return std::make_unique<FourWheeler>();
        default:
            throw std::runtime_error("Invalid Vehicle type");
    }
}    

然后我们将使用这个新函数。
int main()
{
    try
    {
        std::unique_ptr<Vehicle> basePtr = SmarterVehicleFactory(VT_TwoWheeler);
        basePtr->doTypeSpecificStuff();

        basePtr = SmarterVehicleFactory(VT_ThreeWheeler); 
        // unique_ptr freed the TwoWheeler for us.
        basePtr->doTypeSpecificStuff(); 

        basePtr = SmarterVehicleFactory(VT_FourWheeler);
        basePtr->doTypeSpecificStuff(); 

        // just for laughs we will ask for a FiveWheeler, which we have not yet 
        // fully implemented
        basePtr = SmarterVehicleFactory(VT_FiveWheeler);  // will throw exception
        basePtr->doTypeSpecificStuff(); // will not be executed
    }
    catch (const std::exception & exc)
    {
        cerr << "Rats! Something bad happened: " << exc.what();
        // basePtr will be unmodified and still pointing to a FourWheeler
    }
} // basePtr  will go out of scope here and clean up our memory for us.

这种方法的优点在于,没有任何一个类知道其他任何一个类。你可以将VehicleSmarterVehicleFactory原型和车辆类型列表放入头文件中,并隐藏其他所有内容。用户看不到任何东西。所有人都被蒙在鼓里。
为什么这样做很好呢?因为现在你可以更改上述任何一个类(除了Vehicle接口类)而不会对其他任何一个类产生影响。这使得你的代码更易于维护和调试。

0

我正在尝试找到在不使用继承的情况下使用多态的最佳方法,因为我想避免虚拟调用。我一直在寻找改进我目前所拥有的东西的方法(但没有成功),然后我偶然发现了这个问题。到目前为止,这是我能做到的最好的:

template<class VehicleDetails>
class Vehicle {
    VehicleDetails details;

public:
    VehicleDetails& getDetails() {
        return details;
    }

    const VehicleDetails& getDetails() const {
        return details;
    }

    void printDetails() const {
        details.printDetails();
    }
}

class TwoWheeler {
public:
    void printDetails() const {
        cout << "I am two wheeler" << endl;
    }

    void specificTwoWheelerMethod() const {
        cout << "I am specific functionality" << endl;
    }
}

然后你可以这样使用它:

Vehicle<TwoWheeler> vehicle;
vehicle.printDetails(); // prints "I am two wheeler"

不幸的是,这使事情变得复杂了。现在每个需要车辆的类/结构体或函数都必须进行模板化,除非你知道车辆的类型。

template<class VehicleDetails>
void doGeneralVehicleThings(const Vehicle<VehicleDetails>& vehicle) {
    // ...
}

好处在于,当您知道类型时,可以通过getDetails()方法访问特定功能,而无需进行任何强制转换或运行时开销:

void doTwoWheelerThings(const Vehicle<TwoWheeler>& twoWheelerVehicle) {
    twoWheelerVehicle.getDetails().specificTwoWheelerMethod(); // prints "I am specific functionality"
}

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