繁体   English   中英

将抽象C ++类传递给构造函数并调用成员方法

[英]Passing abstract c++ class to constructor and calling member method

是否可以将抽象类作为参数传递并使用其成员函数,如下所示? (摘要:创建了一个需要从基类派生的求解器的模型,并且要求解的方程组可能会发生变化)。

#include <iostream>
#include <string>
#include <functional>

class AbstractSolver {
  public:
  virtual ~AbstractSolver(){}
  virtual double solve() = 0;
  virtual void setSystem(std::function<double(double,double)> system) = 0;
};

class ConcreteSolver : public AbstractSolver {
  protected:
    double stepSize;
    double initialValue;
    std::function<double(double, double)> system;
  public:
  ConcreteSolver(double stepSize,
                 double initialValue) :
     stepSize(stepSize),
     initialValue(initialValue) {}

  double solve() {
    // implementation here ...
  }

  void setSystem(std::function<double(double,double)> system) {
    this->system = system;
  }
};

class Model {
  protected:
    std::function<double(double,double,double)> system;
    AbstractSolver * solver;
  public:
    Model(AbstractSolver * solver,
          std::function<double(double, double, double)> system ) {
      this->solver = solver;
      this->system = system;
    }

    ~Model() {
       delete solver;
    }

    double getSolution(double tau) {
      std::function<double(double, double)> sys =
          [this, tau](double t, double y) { return system(t, y, tau); };
      solver->setSystem(sys); // SIGSEGV
      return solver->solve();
    }
};

int main(){
  std::function<double(double, double, double)> system = 
    [](double t, double y, double tau) {
         return 0;// return some mathematical expression
      };
  AbstractSolver * solver = new ConcreteSolver(0, 1);
  Model model = Model(solver, system);
  model.getSolution(0.1);

}

这将编译,但问题是它在我上面的注释中出现了错误。 谁能解释为什么(我对此一无所获)? 欢迎您的建议

第一个问题:您可以将抽象类用作方法或构造函数的参数-这是多态性的核心。 说完这些,就解决了您的问题。

您的代码中的问题是双重删除/悬挂指针。 正如其他人所指出的那样,您应该遵守“三规则”,并且(更好)使用智能指针而不是原始指针来防止这种情况。

问题开始于:

Model model = Model(solver, system);

使用Model(solver, system)创建一个Model对象,并将其副本分配给model 现在有两个模型对象,但是都共享相同的求解器指针(因为您没有覆盖分配运算符,所以请记住三个规则!)。 当它们的拳头超出范围时,将调用析构函数,并且销毁指针solver 第二个模型对象现在有一个错误的指针。 当第二个对象超出范围时,求解器将再次被释放-以未定义的行为结束。 我的编译器可以原谅,但我什么也看不到。

更糟糕的是,如果释放一次解引用指针后就取消引用,则会导致段错误。 在您的代码中情况并非如此,因为两个对象在main的末尾彼此直接超出范围。 但是,您可以通过以下方式触发段错误:

  Model model = Model(NULL, system);
  {
     model = Model(solver, system);
  }
  model.getSolution(0.1);

现在,第一个模型超出了调用getSolution的范围,因为它的范围在{}之间。 此后,该模型有一个错误的指针,并在调用getSolution某个地方取消了对它的引用。

更好的设计是使用智能指针,而不是原始指针: std::shared_ptr<AbstractSolver>或(取决于您的设计) std::unique_ptr<AbstractSolver>

使用共享指针,您的Model类将如下所示:

class Model {
  protected:
    std::function<double(double,double,double)> system;
    std::shared_ptr<AbstractSolver> solver;
  public:
    Model(const std::shared_ptr<AbstractSolver> &solver,
          std::function<double(double, double, double)> system ) {
      this->solver = solver;
      this->system = system;
    }

   //not needed anymore
   // ~Model() {//nothing to do}

...};

最大的好处是:您根本不需要管理内存,因此不会出错。 最后一个所有者超出范围后,解算器指针将首先删除。 由于三规则,您不需要复制构造函数或赋值运算符,因为您不需要析构函数。

如果您来自Java: shared_ptr行为(几乎)与java的指针完全一样。

如果您的模型拥有(反对共享)求解器,则应使用std::unique_ptr这样一来,它就更加清晰了,并且可以确定,您的求解器没有共享。

有了这个,错误就会消失。 但是您还可以改善其他一些方面:

a)使用私有成员而不是受保护成员:受保护成员的情况下,子类与基类之间的联系更加紧密,私有成员会遇到这种情况-以后更改这些成员要困难得多。

b)不要使用:

Model model = Model(solver, system);

使用:

Model model(solver, system);

这样,您可以直接初始化对象,而无需任何复制,这更快。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM