【问题标题】:Enforce pure virtual function implementation, perhaps with different argument type强制执行纯虚函数实现,可能使用不同的参数类型
【发布时间】:2019-11-23 19:12:50
【问题描述】:

我目前有一个基类Env,它是一个接口,我有几个派生类。

class Env

{
    public:

        //method in question
        virtual std::tuple<Eigen::VectorXf,float,bool,std::string> step(const //what goes here?//)=0;

        virtual ~Env(); //virtual destructor
};

一个示例派生类如下(头)

class MountainCar: public Env

{
    public:

        MountainCar();
        std::tuple<VectorXf,float,bool,std::string> step(const int) override;

};

现在,设计是所有环境都必须从 Env 继承。但是,我想强制所有环境实现 step() 方法,这就是为什么基础 Env 中的 step 方法是纯虚拟的。

但是,每个派生的 Env 可以在 step 方法中采用不同的参数类型,这应该是一个有效的覆盖(这些有效类型来自一个固定的、已知的集合)例如,mountain car 用一个 int 参数定义它。另一个环境 CartPole 将 VectorXf 作为 step 参数。

一开始,我把基类做成了一个带参数U的模板类,并将U传递给step方法。然后,派生类用于继承自模板实例化,例如 MountainCar 继承自 Env。但是,问题是所有派生类都继承自基类的不同实例化,我不能再使用公共基指针来实现多态性。

如何设计这个具有 C++11 特性的系统?

【问题讨论】:

  • 你怎么能用这个?如果另一个类有一个 std::string 作为该方法的参数,你将如何使用基指针调用 step 函数?
  • 由于该类也继承自基类,并且 step 方法是虚拟的,所以我可以调用它。问题是如何使这成为可能(确保派生类覆盖至少采用一种参数类型的 step 方法)
  • 想多了。 base-&gt;step(something); 在您的代码中。你可以传递什么类型的something,如果 base 可以指向一个需要 int 的环境,或者需要一个字符串的东西?
  • 我不明白你的意思。我的基本指针是 Env* MountainCar。然后,我会做 Env->step(1)。
  • 如果你知道最衍生的类型 - 当然。但是,您不需要虚函数,只需使用该派生类型即可。当你收到和Env* 作为函数参数时你会怎么做。

标签: c++ c++11 inheritance polymorphism system-design


【解决方案1】:

你的前提没有多大意义。让我们想象一下这是可能的:

class Base {
    virtual void step(/* something */);
};
class DerivedString : public Base {
    void step(std::string) override;
};
class DerivedInt : public Base {
    void step(int) override;
};

你想让这个做什么?

std::unique_ptr<Base> bs = std::make_unique<DerivedString>();
bs->step(1);  // compiler error? runtime error?

std::unique_ptr<Base> bi = std::make_unique<DerivedInt>();
bi->step("one");  // compiler error? runtime error?

如果您的答案是“编译器错误”,那么您应该删除虚函数和基类,因为它们没有提供任何价值。

如果你的答案是“运行时错误”并且你有 C++17,你可以使用std::any:

class Base {
    virtual void step(std::any);
};
class DerivedString : public Base {
    void step(std::any v) override {
        step(std::any_cast<std::string>(v));
    }
    void step(std::string s);
};
class DerivedInt : public Base {
    void step(std::any v) override {
        step(std::any_cast<int>(v));
    }
    void step(int i);
};

这将导致std::bad_any_cast 被抛到上面。


如果你没有C++17,并且事先知道类型集合,那么你可以预先声明函数的每个重载:

class Base {
    virtual void step(std::string) { throw runtime_error(); }
    virtual void step(int) { throw runtime_error(); }
};
// only subclass from this
template<typename T>
class BaseHelper : public Base {
    void step(T) override = 0; // replace with pure-virtual
}
class DerivedString : public BaseHelper<std::string> {
    void step(std::string s) override;  // compiler error when instantiated if forgotten
};
class DerivedInt : public BaseHelper<int> {
    void step(int i) override;
};

【讨论】:

  • 我喜欢 RaiseNotImplemented Error 解决方案,这在 python 中很常见,唯一的问题是我们不会收到编译时错误。当我们有纯虚函数时,我想如果没有定义 step 方法,我们会得到一个编译时错误。
  • bs-&gt;step(1) 上编译是不可能的,除非在bi-&gt;step(1) 上创建一个,因为bsbi 具有相同的类型。如果它们没有相同的类型,那么你可能有两个不同的基类。
  • step 调用上的编译可能是不可能的,但我说的是派生类根本不实现 step 的一种
  • 现在,所有派生类都有不同的基类(因为它们是模板实例化)。我们现在使用什么通用基指针来调用步骤?
  • 继续使用Base
【解决方案2】:

如果你想通过基类指针或引用调用它,你可以定义一个虚函数。出于任何其他原因这样做是对语言的滥用。

当然,如果您确切知道签名,则只能调用函数。如果不这样做,就没有调用,也没有定义函数。

所以在你的情况下:

class Env {
    public:
       virtual 
         std::tuple<Eigen::VectorXf,float,bool,std::string> 
           step(const // What goes here?
                      // The thing YOU want to pass to this function in YOUR code,
                      // if you have an Env pointer or reference:
                      //     pEnv->step(42);
                      // If you don't have a reason to make such a call,
                      // just don't define the function.
               ) = 0;

【讨论】:

  • 我确实想通过基类指针调用它。除此之外,我还将它设为纯虚拟以强制在所有派生类中实现。而对于“那个东西……”,我的意思是我需要这样,尽管派生类用不同的类型定义了这个函数,函数覆盖仍然有效。
  • @SridharThiagarajan 我确实想通过基类指针调用它。 那么你想传递什么类型的参数呢? “某种类型”不是一个有效的答案。你需要给它命名。如果您大炮,请考虑参数的值应该来自哪里。
  • 参数类型将取决于基指针指向的派生对象的类型。如果是 BasePointer* 字符串类,则有效输入为字符串等。
  • @SridharThiagarajan 参数类型将取决于 Base 指针指向的派生对象的类型。您不知道派生对象的类型。如果您知道,您将使用指向正确派生类型的指针而不是基指针。派生类型可以在您发布代码后很久才创建,这通常是不可能的。那么争论从何而来?仅仅说“它的类型将取决于这个或那个”是不够的。你需要知道类型。
  • 已知类型来自固定集合,如果有帮助的话
猜你喜欢
  • 1970-01-01
  • 2015-07-03
  • 2016-02-18
  • 1970-01-01
  • 2011-05-04
  • 2016-08-13
  • 1970-01-01
  • 2016-03-07
  • 2021-10-26
相关资源
最近更新 更多