从具有不同子类的父指针调用子方法

Abh*_*yal 17 c++ oop inheritance class c++11

我有一个父类,有两个或更多的子类派生自它.随着更多要求的出现,未来不同子类的数量可能会增加,但它们都将遵循基类方案,并且将包含很少的独特方法.让我举一个例子 -

#include <iostream>
#include <string>
#include <vector>
#include <memory>

class B{
    private: int a; int b;
    public: B(const int _a, const int _b) : a(_a), b(_b){}
    virtual void tell(){ std::cout << "BASE" << std::endl; }
};

class C : public B{
    std::string s;
    public: C(int _a, int _b, std::string _s) : B(_a, _b), s(_s){}
    void tell() override { std::cout << "CHILD C" << std::endl; }
    void CFunc() {std::cout << "Can be called only from C" << std::endl;}
};

class D : public B{
    double d;
    public: D(int _a, int _b, double _d) : B(_a, _b), d(_d){}
    void tell() override { std::cout << "CHILD D" << std::endl; }
    void DFunc() {std::cout << "Can be called only from D" << std::endl;}
};

int main() {
    std::vector<std::unique_ptr<B>> v;

    v.push_back(std::make_unique<C>(1,2, "boom"));
    v.push_back(std::make_unique<D>(1,2, 44.3));

    for(auto &el: v){
        el->tell();
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,tell()方法可以正常工作,因为它是虚拟的并且在子类中适当地覆盖.但是现在我无法调用各自类的CFunc()方法和DFunc()方法.所以我脑子里有两个选择 -

  • CFunc()在子类中的一些已经定义的虚方法中包装和朋友,以便它一起执行.但随着数量的增加,我将无法控制特定方法的特定执行.

  • 或者在基类中提供一些纯虚方法,void process() = 0就像它们喜欢的那样在子类中定义它们.可能会被void process(){}一些人留空并被一些人使用.但是再一次感觉不对,因为我在途中失去了回报价值和参数.也像之前的选项一样,如果某些子类中有更多方法,这种方法感觉不正确.

和另一个 -

  • dynamic_cast<>?.这会是一个不错的选择 - 回滚父指针指向子指针(顺便说一下,我在这里使用智能指针,所以只unique/shared允许),然后调用所需的函数.但是,我如何区分不同的子类?另一个公共成员可能会返回一些独特的类枚举值?

我对这种情况非常缺乏经验,并希望得到一些反馈.我该如何处理这个问题?

Ami*_*ory 11

我有一个父类,有两个或更多的子类派生出来......但随着数量的增加,我将无法控制特殊方法的特殊执行.

另一个选项,当预计方法数量增加时有用,并且派生类预期保持相对稳定,是使用访问者模式.以下用途boost::variant.

假设您从三个课程开始:

#include <memory>
#include <iostream>

using namespace std;
using namespace boost;

class b{};
class c : public b{};
class d : public b{};
Run Code Online (Sandbox Code Playgroud)

b您可以使用变体类型,而不是使用指向基类的(智能)指针:

using variant_t = variant<c, d>;
Run Code Online (Sandbox Code Playgroud)

和变量变量:

variant_t v{c{}};
Run Code Online (Sandbox Code Playgroud)

现在,如果您想以不同的方式处理cd方法,您可以使用:

struct unique_visitor : public boost::static_visitor<void> {
    void operator()(c c_) const { cout << "c" << endl; };
    void operator()(d d_) const { cout << "d" << endl; };
};
Run Code Online (Sandbox Code Playgroud)

你打电话给谁

apply_visitor(unique_visitor{}, v);
Run Code Online (Sandbox Code Playgroud)

请注意,您也可以使用相同的机制来统一处理所有类型,方法是使用接受基类的访问者:

struct common_visitor : public boost::static_visitor<void> {
    void operator()(b b_) const { cout << "b" << endl; };
};

apply_visitor(common_visitor{}, v);
Run Code Online (Sandbox Code Playgroud)

请注意,如果类的数量增加的速度快于方法的数量,则此方法将导致维护问题.


完整代码:

#include "boost/variant.hpp"
#include <iostream>

using namespace std;
using namespace boost;

class b{};
class c : public b{};
class d : public b{};

using variant_t = variant<c, d>;

struct unique_visitor : public boost::static_visitor<void> {
    void operator()(c c_) const { cout << "c" << endl; };
    void operator()(d d_) const { cout << "d" << endl; };
};

struct common_visitor : public boost::static_visitor<void> {
    void operator()(b b_) const { cout << "b" << endl; };
};

int main() {
    variant_t v{c{}};
    apply_visitor(unique_visitor{}, v);
    apply_visitor(common_visitor{}, v);
}
Run Code Online (Sandbox Code Playgroud)


Nik*_*pol 9

您可以为每个设备类声明具有纯方法的接口.定义特定设备实现时,仅从对其有意义的接口继承.

使用您定义的接口,您可以迭代并调用特定于每个设备类的方法.

在下面的示例中,我声明了一个HardwareInterface将由所有设备继承的设备,并且AlertInterface只能由可以物理警告用户的硬件设备继承该设备.其它类似的接口可以被定义,例如SensorInterface,LEDInterface

#include <iostream>
#include <memory>
#include <vector>

class HardwareInteface {
    public:
        virtual void on() = 0;
        virtual void off() = 0;
        virtual char read() = 0;
        virtual void write(char byte) = 0;
};

class AlertInterface {
    public:
        virtual void alert() = 0;
};

class Buzzer : public HardwareInteface, public AlertInterface {
    public:
        virtual void on();
        virtual void off();
        virtual char read();
        virtual void write(char byte);
        virtual void alert();
};

void Buzzer::on() {
    std::cout << "Buzzer on!" << std::endl;
}

void Buzzer::off() {
    /* TODO */
}

char Buzzer::read() {
    return 0;
}

void Buzzer::write(char byte) {
    /* TODO */
}

void Buzzer::alert() {
    std::cout << "Buzz!" << std::endl;
}

class Vibrator : public HardwareInteface, public AlertInterface {
    public:
        virtual void on();
        virtual void off();
        virtual char read();
        virtual void write(char byte);
        virtual void alert();
};

void Vibrator::on() {
    std::cout << "Vibrator on!" << std::endl;
}

void Vibrator::off() {
    /* TODO */
}

char Vibrator::read() {
    return 0;
}

void Vibrator::write(char byte) {
    /* TODO */
}

void Vibrator::alert() {
    std::cout << "Vibrate!" << std::endl;
}

int main(void) {
    std::shared_ptr<Buzzer> buzzer = std::make_shared<Buzzer>();
    std::shared_ptr<Vibrator> vibrator = std::make_shared<Vibrator>();

    std::vector<std::shared_ptr<HardwareInteface>> hardware;
    hardware.push_back(buzzer);
    hardware.push_back(vibrator);

    std::vector<std::shared_ptr<AlertInterface>> alerters;
    alerters.push_back(buzzer);
    alerters.push_back(vibrator);

    for (auto device : hardware)
        device->on();

    for (auto alerter : alerters)
        alerter->alert();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

根据各个传感器类型AccelerometerInterface,接口可以更加具体:GyroscopeInterface,等等.

  • 我用这种方法看到的问题是现在OP必须维护多个容器而不是像示例中给出的单个容器.但这只是插入和删除的情况,因为更新将由于共享的<ptr>而不是对象的副本而反映出来. (2认同)
  • 拥有多个容器的"问题"不是问题,而是一个优点.在所有项目必须被投射,测试然后最终使用之前,现在只有实现该接口的项目将被迭代.也就是说,不知道为什么这些物品放在一个容器中,以及它们将如何使用,很难肯定. (2认同)

UKM*_*key 8

虽然你的要求是可能的,但它会导致你的代码散布在演员表中,或者在没有意义的类上可用的函数.两者都不可取.如果您需要知道它是C类还是D类,那么很可能将它存储为B是错误的,或者您的接口B是错误的.

多态性的全部意义在于使用B的事情是他们不需要确切地知道它是什么类型的B. 对我来说,这听起来像是在扩展课程而不是将它们作为成员,即"C是B"没有意义,但"C有B做".

我会回过头来重新考虑B,C,D和所有未来项目的作用,以及为什么他们拥有你需要调用的这些独特功能; 并研究函数重载是否是您真正想要做的.(类似于Ami Tavory对访客模式的建议)


小智 6

你可以unique_ptr.get()用来获取Unique Pointer中的指针,并使用指针作为normall.像这样:

for (auto &el : v) {
        el->tell();
        D* pd = dynamic_cast<D*>(el.get());
        if (pd != nullptr)
        {
            pd->DFunc();
        }
        C* pc = dynamic_cast<C*>(el.get());
        if (pc != nullptr)
        {
            pc->CFunc();
        }
    }
Run Code Online (Sandbox Code Playgroud)

结果如下:

CHILD C
Can be called only from C
CHILD D
Can be called only from D
Run Code Online (Sandbox Code Playgroud)


Haz*_*maa 5

  • 如果可以隐藏尽可能多的特定于类型的实现细节,则应使用第一种方法.

  • 然后,如果你需要公共接口,你应该使用虚拟功能(你的第二种方法),并避免使用dynamic_cast(你的第三种方法).许多theads可以告诉你为什么(例如Polymorphism vs DownCasting).你已经提到了一个很好的理由,就是你不应该真正检查对象类型......

  • 如果您的虚拟功能有问题,因为您的驱动类有太多独特的公共接口,那么它不是IS-A关系,是时候审查您的设计了.例如,对于共享功能,请考虑组合,而不是继承......