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)
这将编译,但问题是它在我上面放置评论的地方出现了段错误。谁能解释为什么(我找不到与此相关的任何内容)?欢迎您提出建议
对于你的第一个问题:你可以将抽象类作为方法或构造函数的参数 - 这是多态性的核心。说完这些,就开始解决你的问题了。
您的代码中的问题是双重删除/悬挂指针。正如其他人指出的那样,您应该遵守三法则并(更好)使用智能指针而不是原始指针来防止这种情况。
问题从以下行开始:
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)
这样就可以直接初始化对象,不需要任何复制,速度更快。
| 归档时间: |
|
| 查看次数: |
852 次 |
| 最近记录: |