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

A.I*_*.I. 2 c++ inheritance parameter-passing

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

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

}
Run Code Online (Sandbox Code Playgroud)

这将编译,但问题是它在我上面放置评论的地方出现了段错误。谁能解释为什么(我找不到与此相关的任何内容)?欢迎您提出建议

ead*_*ead 5

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

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

问题从以下行开始:

Model model = Model(solver, system);
Run Code Online (Sandbox Code Playgroud)

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

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

  Model model = Model(NULL, system);
  {
     model = Model(solver, system);
  }
  model.getSolution(0.1);
Run Code Online (Sandbox Code Playgroud)

现在,第一个模型在调用之前超出了范围,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}
Run Code Online (Sandbox Code Playgroud)

... };

最大的收获是:你根本不需要管理内存,因此不会犯错误。当最后一个所有者超出范围后,解算器指针首先被删除。由于三法则,您既不需要复制构造函数也不需要赋值运算符,因为您不需要析构函数。

如果你来自java:shared_ptr行为(几乎)与来自 java 的指针完全相同。

如果您的模型拥有(而不是共享)求解器,您应该使用std::unique_ptr- 这样会更清晰,并且您可以确定您的解算器没有共享。

这样你的错误就会消失。但还有其他一些点可以改进:

a) 使用私有成员而不是受保护成员:对于受保护成员,子类与基类的耦合更紧密,就像私有成员的情况一样 - 以后更改这些成员要困难得多。

b) 不要使用:

Model model = Model(solver, system);
Run Code Online (Sandbox Code Playgroud)

而是使用:

Model model(solver, system);
Run Code Online (Sandbox Code Playgroud)

这样就可以直接初始化对象,不需要任何复制,速度更快。