在运行时从任何多个类中选择性地继承

时间:2017-07-08 04:34:57

标签: c++ oop inheritance

我正在进行科学计算,而且我是c ++的初学者。 MyNLP是一个包含所有问题数据和方法的类。我正在使用第三方库进行数值优化。每个第三方都是一个解决我问题的特定算法。为了使用每个库,我的MyNLP类需要从第三方库继承相应的类。

例如,

Class MyNLP :public IPOPT
{
};

让我使用IPOPT算法来解决我的问题。同样,

class MyNLP: public SQP
{
};

让我使用SQP算法。

但就我而言,只有在运行时,程序才会决定它应该继承哪个类。我必须继承第三方课程之一。 任何人都可以在cpp中实现这一目标吗?

4 个答案:

答案 0 :(得分:25)

您无法在运行时选择继承,因为结果类型始终由编译器在编译时确定。

您可以做的是应用Strategy Pattern

enter image description here

这个想法是有一个抽象类来表示MyNLP中使用的算法:

class Data;

class NLPAlgo {
public:
    virtual ~NLPAlgo() = default;
    virtual void Apply(Data&) = 0;
};

并提供使用IPOPTSQP的具体类:

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个不同的具体或完整类型类AB,&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你可能需要直接调用或使用它们,你将无法访问它们,但好处是你可以访问任何东西那是publicprotected,但这是数据封装的想法。

答案 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的所有相关方法的基类:

  • 目标价值
  • 目标渐变
  • (in)等式约束的雅可比行列式
  • 拉格朗日
  • 拉格朗日的黑森州

由于您的求解器可能会使用等效格式的标量,向量以及希望稀疏矩阵(以坐标格式:行向量,列向量,值向量)和相同的浮点类型(双精度),然后您可以实现轻量级围绕这个基类的包装器(基于求解器的接口),而不是试图使用某种奇怪的运行时多态性。