我正在进行科学计算,而且我是c ++的初学者。 MyNLP
是一个包含所有问题数据和方法的类。我正在使用第三方库进行数值优化。每个第三方都是一个解决我问题的特定算法。为了使用每个库,我的MyNLP
类需要从第三方库继承相应的类。
例如,
Class MyNLP :public IPOPT
{
};
让我使用IPOPT算法来解决我的问题。同样,
class MyNLP: public SQP
{
};
让我使用SQP算法。
但就我而言,只有在运行时,程序才会决定它应该继承哪个类。我必须继承第三方课程之一。 任何人都可以在cpp中实现这一目标吗?
答案 0 :(得分:25)
您无法在运行时选择继承,因为结果类型始终由编译器在编译时确定。
您可以做的是应用Strategy Pattern:
这个想法是有一个抽象类来表示MyNLP
中使用的算法:
class Data;
class NLPAlgo {
public:
virtual ~NLPAlgo() = default;
virtual void Apply(Data&) = 0;
};
并提供使用IPOPT
和SQP
的具体类:
class IPOPTAlgo : public NLPAlgo {
IPOPT ipopt;
public:
void Apply(Data& data) {
// Use ipopt to realize the implementation
}
};
class SQPAlgo : public NLPAlgo {
SQP sqp;
public:
void Apply(Data& data) {
// Use sqp to realize the implementation
}
};
进一步将该抽象类作为MyNLP
class MyNLP {
std::unique_ptr<NLPAlgo> algo_;
public:
MyNLP(std::unique_ptr<NLPAlgo> algo) : algo_(algo) {}
void Apply(Data& data) {
algo->Apply(data);
}
};
然后,您可以在运行时配置哪个算法应与MyNLP
一起使用:
// Note:
// That code could be factored out to an Abstract Factory:
// https://sourcemaking.com/design_patterns/abstract_factory
// That is figured out in more detail in this answer:
// https://stackoverflow.com/a/44985054/8242698
std::unique_ptr<NLPAlgo> algo;
if(condIPOPT) {
algo = std::make_unique<IPOPTAlgo>();
}
else if(condSQP) {
algo = std::make_unique<SQPAlgo>();
}
Data data;
MyNLP myNLP(algo);
myNLP.Apply(data);
答案 1 :(得分:7)
有一点模板魔法(在编程中没有魔法这样的东西)我认为这会帮助你以你所要求的方式实现你的目标。还有许多其他很好的答案,如策略模式,工厂,调度等,但这是一个使用模板和继承来自所述库的版本,同时选择通过使用模板专业化在运行时实例化。
#include <iostream>
class A {
public:
int a = 1;
A() {}
};
class B {
public:
float b = 2.0f;
B() {}
};
class C {
public:
char c = 'c';
C() {}
};
template<class T>
class D : public T {
public:
D() : T() {}
};
int main( int argc, char** argv ) {
D<A> dA;
D<B> dB;
D<C> dC;
std::cout << "D<A>::a = " << dA.a << "\n";
std::cout << "D<B>::b = " << dB.b << "\n";
std::cout << "D<C>::c = " << dC.c << "\n";
std::cout << "Press any key and enter to quit." << std::endl;
char c;
std::cin >> c;
return 0;
}
这里我展示了3个不同的具体或完整类型类A
,B
,&amp; C
可以代表您用于执行评估或计算以解决问题的3种不同的库。类D
是代表您的MyNLP
类的模板类型。现在,您可以让MyNLP<A> mynlpA
使用第一个库,因为您的类现在将继承它,依此类推。
然而;这是在编译时而不是运行时完成的,您必须使用特定类型实例化该类。您可以使用此模板并通过if语句或某些已定义函数中的switch语句使用用户输入进行设置,以选择在运行时创建和使用的类的哪个版本。另请注意,我基于继承类的基类专门设计了不同的类模板构造函数。运行此代码段以了解我如何根据运行时的用户输入让class template D<T>
继承A, B, or C
。
#include <iostream>
#include <string>
#include <algorithm>
class A {
public:
int a = 1;
A() {}
};
class B {
public:
float b = 2.0f;
B() {}
};
class C {
public:
char c = 'c';
C() {}
};
template<class T>
class D : public T {
public:
D() : T() {}
};
template<>
D<A>::D() {
std::cout << "Initialize Library A\n";
}
template<>
D<B>::D(){
std::cout << "Initialize Library B\n";
}
template<>
D<C>::D() {
std::cout << "Initialize Library C\n";
}
int main( int argc, char** argv ) {
std::string entry;
std::cout << "Please choose which library to chose from: `A`, `B` or `C`\n";
std::cout << "Or `Q` to quit\n";
std::cin >> entry;
std::transform(entry.begin(), entry.end(), entry.begin(), ::toupper);
while ( true ) {
if (entry == std::string("Q")) {
break;
}
if (entry == std::string("A")) {
D<A> dA;
std::cout << "D<A>::a = " << dA.a << "\n";
}
if (entry == std::string("B")) {
D<B> dB;
std::cout << "D<B>::b = " << dB.b << "\n";
}
if (entry == std::string("C")) {
D<C> dC;
std::cout << "D<C>::c = " << dC.c << "\n";
}
entry.clear();
std::cout << "Please choose which library to chose from: `A`, `B` or `C`\n";
std::cout << "Or `Q` to quit\n";
std::cin >> entry;
std::transform(entry.begin(), entry.end(), entry.begin(), ::toupper);
}
std::cout << "Press any key and enter to quit." << std::endl;
char c;
std::cin >> c;
return 0;
}
但是这个方法有一个警告:如果基类有private members/functions
你可能需要直接调用或使用它们,你将无法访问它们,但好处是你可以访问任何东西那是public
或protected
,但这是数据封装的想法。
答案 2 :(得分:5)
C ++类继承是一个编译时构造:它在运行时被修复。编译器必须在编译时将此信息提供给它,以确定如何分配对象并确保类型安全。
您应该考虑改为实施factory。在此设计中,工厂对象或函数根据运行时可用的输入决定生成哪个实例。您应该创建一个公共基类,它提供所需功能的接口。这应该包含一个或多个用于动态调用正确实现的virtual functions(这称为dynamic dispatch)。
以下是基于您的问题的简单示例:
class nlp_interface
{
public:
virtual ~nlp_interface() = default;
// This pure-virtual function has no implementation, forcing the
// class to be abstract and for derived classes to implement the
// member function.
virtual void do_numeric_optimization(/* ... */) = 0;
};
class MyNLP_IPOPT:
public nlp_interface,
public IPOPT
{
public:
// Provide a specific implementation.
virtual void do_numeric_optimization(/* ... */);
};
class MyNLP_SQP:
public nlp_interface,
public SQP
{
public:
// Provide a specific implementation.
virtual void do_numeric_optimization(/* ... */);
};
这里我使用多重继承来为MyNLP_*
类提供一个众所周知的接口。这是因为我不能假设第三方基类具有可以使用的公共虚拟接口。如果他们这样做,那么直接创建第三方类的实例。但是,您似乎暗示了由于某种原因必须将它们子类化。
这是工厂。
#include <memory>
#include <exception>
using nlp_pointer = std::unique_ptr<nlp_interface>;
nlp_pointer factory_function(const std::string& input)
{
if (input == "IPOPT") {
return nlp_pointer( new MyNLP_IPOPT );
}
if (input == "SQP") {
return nlp_pointer( new MyNLP_SQP );
}
throw std::runtime_error("unrecognized algorithm kind");
}
要使用工厂,请调用factory_function()
并在返回的do_numeric_optimization()
实例(包含在智能指针中)上调用nlp_interface
成员函数。它将通过动态调度调用正确的版本。
答案 3 :(得分:5)
在我看来,这是XY problem的一个实例: 您想要一种方法来切换基类,而实际上想要一种方法将代码用于不同的解算器而几乎不需要适应。
最近我自己用IPOPT实现了NLP求解器,我建议采用另一种方法:
为什么不首先实现一个实现评估NLP的所有相关方法的基类:
由于您的求解器可能会使用等效格式的标量,向量以及希望稀疏矩阵(以坐标格式:行向量,列向量,值向量)和相同的浮点类型(双精度),然后您可以实现轻量级围绕这个基类的包装器(基于求解器的接口),而不是试图使用某种奇怪的运行时多态性。