【问题标题】:Specialization of pure virtual base method in derived class派生类中纯虚基方法的特化
【发布时间】:2019-02-07 14:48:29
【问题描述】:

我想知道是否有一种通用的方式/模式可以让派生类在给定的基类中拥有更专业的纯虚方法版本。

class Base {
public:
    Base() = default;
    virtual ~Base() = 0;
    virtual void foo(int bar) = 0;
};
inline Base::~Base() {}

class Derived public Base {
public:
    Derived() = default;
    ~Derived() = default;
    void foo(int bar) override {/*...*/}
};

class SpecializedDerived : public Base {
public:
    SpecializedDerived() = default;
    ~SpecializedDerived() = default;
    void foo(int bar, double additionalParameter) override {/*...*/}
};

SpecializedDerived 类中的覆盖是不可能的,因为方法的签名与纯虚拟Base 类中的签名不对应。

现在,有没有办法实现所描述的设计?有没有办法实现“更专业的方法”,因为有类继承可以让您实现“更专业的类”?

在打字时,我认为我的愿望更像是一种“伙计,我只是希望你提供某种iterate(.) 功能!”东西。

到目前为止,我想到的唯一想法是

class Base {
public:
    Base() = default;
    virtual ~Base() = 0;
    virtual void foo(int bar) = 0;
};
inline Base::~Base() {}

class SpecializedDerived : public Base {
public:
    SpecializedDerived(double addParam) : additionalParam_(addParam) {}
    ~SpecializedDerived() = default;
    void foo(int bar) override {
        iterate(bar, additionalParam_);
        return;
    }
private:
    double additionalParam_;
    void foo(int bar, double additionalParam) {/*...*/}
};

这个内部函数调用实际上是多余的,你可以这样做:

class SpecializedDerived : public Base {
public:
    SpecializedDerived(double addParam) : additionalParam_(addParam) {}
    ~SpecializedDerived() = default;
    void foo(int bar) override {/* code using additionalPara_ */}
private:
    double additionalParam_;
};

【问题讨论】:

  • 只看到Base*Base& 的代码将如何调用“更专业”的方法?它如何传递那个额外的参数?
  • 你的解决方案有什么问题?
  • 从技术上讲,您可以在Base 中使用virtual void foo(int bar, double additionalParameter) = 0,在Derived 中使用void foo(int bar, double additionalParameter = 0) override
  • 我不明白你的用例。如果应该有一个额外的参数,为什么Base中没有一个?
  • @StoryTeller:你是完全正确的......在Base* b = new SpecializedDerived(); 的情况下无法实现这样的事情的一个非常明显的原因@ --- @Christian Hackl:现在你'再问……其实没什么。我只是不想让附加参数成为该类的成员。 --- @PasserBy:在这种情况下,例如它是关于一个抽象基类Iterable,用于计算模拟步骤。 Iterable 类越专业,它可能需要的参数就越多,我想“强制”用户安装某种 iterate(.) 方法。

标签: c++ inheritance polymorphism pure-virtual


【解决方案1】:

从阅读Wikipedia+ contravariance 开始。这个想法是孩子可以用一个接受更广泛接口的函数来覆盖一个函数。该函数必须接受旧接口,但也可以扩展它。这个想法不是为了打破对具有基类的引用/指针的代码的期望。

请注意,目前 C++ 不支持逆变,所以这只是一个学术讨论。

代码中的覆盖函数不接受对旧接口的调用,因此它不算作逆变。 覆盖:

virtual void foo(int bar):

覆盖:

void foo(int bar, double additionalParameter) override {/*...*/} };

正确的方法是在 C++ 中手动遵循逆变原则。这或多或少是你想要做的。您需要覆盖:

void foo(int bar) override

并使其调用带有附加参数的函数。对于类的用户来说,这似乎是逆变(带有一个额外的默认参数)。

注意:用常规函数重载虚函数是危险的,可能会导致问题。重载函数会隐藏父函数中具有相似名称的函数。除非小心处理,否则这可能会导致混乱和错误。 override 关键字使问题不那么严重,但仍然存在一些风险。

【讨论】:

    【解决方案2】:

    为什么需要 matchnig 签名?

    多态性和虚函数背后的想法是,调用者不必知道它使用的对象的真实类的任何细节:

    例子:

    Base *my_object = find_dynamically_a_relevant_object (...);              
    my_object->foo(10);   // could be a Derived or a SpecializedDerived
    
    std::vector<Base*> container;  
    ...                   // somehow you populate the container with a mix of 
    ...                   // Derived AND SpecializedDerived objects 
    for (auto x: container)
        x->foo(std::srand());  
    

    这就是为什么签名必须与基类中定义的完全匹配的原因。

    但是也可以使用不同的签名吗?

    现在您可以很好地定义一个重载的foo(),在三种情况下具有完全不同的签名:

    • 不会override:它是同名的不同函数。 * 只有当您确定对象具有正确的类型时,您才能使用其附加参数调用重载的 foo()。
    • 您必须确保提供了与提供匹配签名的覆盖(因为它是纯虚拟的)。例如,这可能只是调用您的重载,对额外参数使用一些任意值)

    例子:

    class SpecializedDerived : public Base {
    public:
        SpecializedDerived() = default;
        ~SpecializedDerived() = default;
        void foo(int bar) override { foo(bar, 0.0); }
        void foo(int bar, double additionalParameter)  {cout<<"specialized "<<bar<<" "<<additionalParameter<<endl;}
    };
    
    ... // elsewhere, for example in main(): 
    
    SpecializedDerived my_obj;  
    my_obj.foo(10);  // use the override of the base
    my_obj.foo(10, 37.2); // use the overload
    
    // suppose that p is a Base* like in the first example
    auto *q = dynamic_cast<SpecializedDerived*>(p); 
    if (q)  // dynamic cast makes this nullptr if not the right type
        q->foo(10, 37.2); 
    else cout << "Not specialized"<<endl; 
    

    根据一些附加数据覆盖行为

    现在如果你想在严格的多态上下文中使用你的 foo() 但仍然有一些(隐藏的)附加参数,有几种可能性,例如:

    • 您可以扩展基本签名并添加一个附加的、大部分未使用的参数和一个默认值。在大多数情况下,这是一个坏主意:如果一个新的派生类带有另一个参数怎么办。
    • 您可以在调用之前注入附加参数(按照您自己的建议在构造时注入,或者在需要时使用设置器更改值)。适用于大多数情况。唯一的风险是确保在调用foo()之前正确设置了附加参数
    • 您可以更改签名以将包含所有实际参数的对象用作单个参数。这需要一些额外的开销,但对于可变参数非常灵活。唯一的问题是这个对象的类型很可能也需要是多态的,在这种情况下,您必须确保为正确的对象使用正确的参数类型。在我看来,它超级强大,但超级超级冒险。

    【讨论】:

    • 感谢这个非常有趣的答案以及所有这些关于如何做到这一点的想法!关于最后一个想法,多态参数数据类,我也想过,但我不喜欢这个想法,因为方法签名(所有派生类都相同)没有显示应该使用哪个专门的参数类称它为。你能想出解决这个问题的办法吗?就在我的脑海中,可以创建纯虚方法protected 并通过需要正确参数子类的public 方法重载来调用它。但是话又说回来,我们的方式隐藏了方法
    • 是的,这就是问题所在。如果您对不同的派生类有不同的参数,那么您将不再具有多态精神。所以无论你做什么,你都必须担心匹配参数/真实类型。就个人而言,我会尝试设计没有参数变化的多态。如果不可能,请选择第二个项目符号,并覆盖 foo() 以使用注入参数调用非多态变体。然后,如果需要,您可以进行多态调用(以及一些额外的体操),但只要对象是已知类型,我就会使用直接参数调用真正的方法。
    • 我在上一条评论中提出的由于方法隐藏是不可能的,对吧?
    • @LCsa 使虚拟保护不会改变这种情况。它只会改变你可以直接用类做的事情。对于任何重载或特定参数,pb 是您不会在基类 poibter 中看到它们,因此您必须确定类型并逐个使用特定成员
    • 啊,sh*t,我刚刚在compile online上写了一些example code来实践第三个子弹,当我刚刚意识到它破坏了能够通过a的巨大优势时父/子对象列表并调用虚拟 foo 函数... >.> 但也许它向您展示了我想尝试使实际方法受到保护,从而迫使程序员确保使用正确的参数对象进行调用。跨度>
    猜你喜欢
    • 1970-01-01
    • 2019-08-31
    • 1970-01-01
    • 1970-01-01
    • 2018-01-25
    • 1970-01-01
    • 1970-01-01
    • 2016-10-14
    • 1970-01-01
    相关资源
    最近更新 更多