Выборочное наследование от любого из нескольких классов во время выполнения

Я занимаюсь научными вычислениями, и я начинаю на С++. MyNLP - это класс, содержащий все данные и методы проблемы. Я использую сторонние библиотеки для численной оптимизации. Каждая третья сторона является конкретным алгоритмом для решения моей проблемы. Чтобы использовать каждую библиотеку, мой класс MyNLP должен наследовать соответствующий класс из сторонней библиотеки.

Например,

Class MyNLP :public IPOPT
{
};

Позвольте мне использовать алгоритм IPOPT для решения моей проблемы. Аналогично,

class MyNLP: public SQP
{
};

Позвольте мне использовать алгоритм SQP.

Но в моем случае, только во время выполнения программа решает, какой класс он должен наследовать. Я должен унаследовать один из сторонних классов. Может ли кто-нибудь дать технику для достижения этого в cpp?

Ответ 1

Вы не можете выбрать наследование во время выполнения, потому что результирующий тип всегда определяется компилятором во время компиляции.

Что вы можете сделать, это применить шаблон :

введите описание изображения здесь

Идея состоит в том, чтобы иметь абстрактный класс, который представляет алгоритм, используемый в 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);

Ответ 2

С небольшим количеством магии шаблонов (ну, в программировании нет такой вещи, как магия). Думаю, это поможет вам достичь своей цели в том виде, в котором вы просили. Там было много других замечательных ответов, таких как "Шаблон стратегии", "w770", "Диспетчерский вызов" и т.д., Но это версия, которая использует шаблоны и наследование из указанной библиотеки, в то время как вы выбираете, какой из них следует создавать во время выполнения, используя специализированную специализацию.

#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 и 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, но это идея инкапсуляции данных.

Ответ 3

Наследование класса С++ - это конструкция времени компиляции: она исправлена ​​во время выполнения. Компилятор должен иметь эту информацию для него во время компиляции, чтобы определить, как распределять объекты и обеспечивать безопасность типов.

Вместо этого вы должны использовать factory. В этом проекте объект или функция factory решает, какой экземпляр будет создан на основе ввода, доступного во время выполнения. Вы должны создать общий базовый класс, который обеспечивает интерфейс к требуемой функциональности. Это должно состоять из одной или нескольких виртуальных функций, которые используются для динамического вызова правильной реализации (это называется динамическая отправка).

Вот пример, основанный на вашем вопросе:

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_*. Это связано с тем, что я не могу предположить, что базовые классы сторонних разработчиков имеют общий виртуальный интерфейс, который можно использовать. Если они это сделают, просто создайте экземпляры сторонних классов напрямую. Однако вы, похоже, ссылались на то, что вам приходилось подклассифицировать их по той или иной причине.

Здесь factory.

#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, вызовите factory_function() и вызовите функцию-член do_numeric_optimization() в возвращаемом экземпляре nlp_interface (который завернут в интеллектуальный указатель). Он будет вызывать правильную версию с помощью динамической отправки.

Ответ 4

Мне кажется, что это пример проблемы XY: Вы попросили способ отключить базовые классы, в то время как вы действительно хотите использовать свой код для разных решателей с небольшой адаптацией.

Внедрив в последнее время решение NLP с IPOPT, я бы рекомендовал другой подход:

Почему бы вам не сначала реализовать базовый класс, который реализует все соответствующие методы для оценки НЛП:

  • Объективное значение
  • Объективный градиент
  • Якобиан ограничений равенства (in)
  • лагранжиан
  • Гессиан лагранжиана

Так как ваши решатели, скорее всего, будут использовать эквивалентные форматы для скаляров, векторов, а также, надеюсь, разреженных матриц (в формате координат: вектор строки, вектор-столбец, вектор значений) и один и тот же тип с плавающей запятой (double), тогда вы можете реализовать легкий (основанный на интерфейсе решателя) вокруг этого базового класса вместо того, чтобы пытаться использовать какой-то странный полиморфизм во время выполнения.