【问题标题】:Passing abstract c++ class to constructor and calling member method将抽象 C++ 类传递给构造函数并调用成员方法
【发布时间】:2016-01-14 20:28:48
【问题描述】:

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

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

}

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

【问题讨论】:

  • learn how to use a debugger 的绝佳机会。
  • 您需要关注Rule of Three。由于您缺少复制构造函数/复制分配,因此在创建、复制和销毁临时模型后,它会删除您的求解器。
  • @ead 它是用 C++ 编码的。我在这里只放了相关部分,实际代码更大更复杂,所以我试图抓住本质。我意识到我在那里犯了几个错误......我已经更新了上面的内容:对不起,我有点累了。
  • 这段代码显示了最可怕的模式。您在构造函数中接受一个指针,然后在析构函数中删除它。谁给你保证这个指针是可删除的?删除所有糟糕的指针,用unique_ptrs 替换它们,这些问题就会消失。
  • @EdHeal 这就是为什么它是一个指针

标签: c++ inheritance parameter-passing


【解决方案1】:

对于您的第一个问题:您可以将抽象类作为方法或构造函数的参数 - 这是多态性的核心。说了这么多,继续你的问题。

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

问题从以下行开始:

Model model = Model(solver, system);

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

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

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

现在,第一个模型在调用getSolution 之前就超出了范围,因为它的范围在{} 之间。此后,模型有一个错误的指针,并在调用 getSolution 时将其取消引用。

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

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

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

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

【讨论】:

  • 太棒了。非常感谢!
猜你喜欢
  • 2021-05-08
  • 2019-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多