派生类中纯虚拟基方法的专业化

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了派生类中纯虚拟基方法的专业化相关的知识,希望对你有一定的参考价值。

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

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_;
};
答案

Why is a matchnig signature necessary ?

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

例:

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());  

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

But can a different signature also be used ?

现在你可以很好地在三个条件下定义一个具有完全不同签名的重载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; 

Override with a behavior depending on some adiditional data

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

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

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

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

代码中的覆盖函数不接受对旧接口的调用,因此不会将其视为逆变。覆盖:

virtual void foo(int bar):

重写:

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

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

void foo(int bar) override

并使用附加参数调用该函数。对于类的用户来说,它似乎是逆变(具有附加的默认参数)。

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

以上是关于派生类中纯虚拟基方法的专业化的主要内容,如果未能解决你的问题,请参考以下文章

在派生类中使用来自虚拟基类的受保护 ctor

除了使用纯虚拟外,如何通过基类从派生类中获取成员

在使用基类中的方法时使用派生类中的属性?

为啥基类指针指向基类中的纯虚方法,而不是派生类中的覆盖方法?

在基类和派生类中使用基类的装饰器

派生自抽象基类并调用另一个类中的方法的 C++ 包装类,该类也派生自抽象基类