Downcasting с использованием не виртуального интерфейса вдоль класса шаблона

Я реализую код конечного элемента.

В методах конечных элементов нам нужны интегратор и интерполятор. Интегратор — это объект, который выполняет численное интегрирование геометрического объекта, например четырехугольника, треугольника и т. Д. Интегратор размещает несколько точек интегрирования или абсцисс внутри геометрических объектов, а затем использует интерполятор для аппроксимации значения функция в этих точках интеграции.

Например, четырехсторонний объект может использовать интегратор, который использует 4 точки интеграции.

* ------------- *
|               |
|    @     @    |
|               |
|               |
|    @     @    |
|               |
* ------------- *

где @ представляет местоположение точек интеграции. Интерполятор аппроксимирует значение функции в этих точках интеграции, используя значения в угловых узлах, представленных *. Вы можете думать об этом как о том, что каждое значение @ является своего рода средним значением всех *.

Обзор полиморфизма

Следующая таблица показывает для удобства связь между различными классами, используемыми в этом вопросе:

               Interpolator
|
| is a base for:
v
Interpolator_Template
|
| is a base for:
|
------------------------------------------
|                              |         |
|                              |    More shapes
V                              V
Interpolator_Quad         Interpolator_Tria
|    |                         |
|   ... More QUADs         More TRIAs
v
Interpolator_Quad_04Integrator
|
| has member variable
v
vector<Abscissa*> abscissa
|
| Abscissa is a base class for:
|
--------------------------------
|               |              |
|          More shapes         |
V                              V
Abscissa_Quad               Abscissar_Tria

Каждая геометрическая форма имеет свою систему координат, поэтому мой интегратор и абсциссы выглядят так:

Integrator.hpp

class Integrator {

public:

void
integrate();

private:

/**
* An interpolator class.
*/
Interpolator * _interpolator;

/**
* List of abscissae (for the quadrilateral shown above, _abscissae.size() == 4).
*/
std::vector< Abscissa * > _abscissae;
};

Базовый класс для всех естественных координат абсцисс.

Abscissa.hpp

class Abscissa {

};

Четырехсторонняя абсцисса действует на естественные координаты ξ и η.

Abscissa_Quad.hpp

class Abscissa_Quad final : public Abscissa {

public:

const double xi;

const double eta;
};

Абсцисса треугольника действует на натуральные координаты ζ1, ζ2 и ζ3.

Abscissa_Tria.hpp

class Abscissa_Tria final : public Abscissa {

public:

const double zeta_1;

const double zeta_2;

const double zeta_3;
};

Реализация интегратора будет интегрирована примерно так:

Integrator.cpp

void
Integrator::integrate()
{
for ( Abscissa * abscissa : _abscissae ) {
_intepolator->eval_det_J( abscissa );
}
}

Все идет нормально. Позвольте мне показать вам мой класс интерполятор.

Interpolator.hpp

class Interpolator {

public:

/**
* Evaluate the determinant of the Jacobian (important for numerical integration).
*
* @note Common interface for all abscissa types.
*
* @param[in] abscissa Integration abscissa.
* @return Shape functions.
*/
virtual double
eval_det_J(
const Abscissa * abscissa ) const = 0;
};

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

Interpolator_Quad.hpp

class Interpolator_Quad : public Interpolator_Template< Abscissa_Quad > {

public:

// ... More functions common to all quadrilaterals.
};

Этот производный класс соответствует четырехугольнику, нарисованному в начале этого вопроса.
Причина, по которой он получен, заключается в том, что могут существовать четырехугольники с большим количеством узлов интерполяции.
Этот класс реализует элемент QUAD_04 (четырехугольник с 4 узлами интерполяции), но в конечных элементах у нас также есть QUAD_08, QUAD_09 и т. Д.

Interpolator_Quad_04.hpp

class Interpolator_Quad_04 final : public Interpolator_Quad {

public:

double
eval_det_J(
const Abscissa_Quad * abscissa ) const;
};

Interpolator_Quad_04.cpp

double
Interpolator_Quad_04::eval_det_J(
const Abscissa_Quad * abscissa ) const
{
// Important! Get coordinates from an Abscissa_Quad object.
const double xi = abscissa.xi;
const double eta = abscissa.eta;

double det_J = ...
// ... Perform some computations and return the determinant of the Jacobian.

return det_J;
}

Позвольте мне вернуться к классу Interpolator_Template, который я пропустил, чтобы объяснить ранее. В какой-то момент в моем коде я выполняю понижение с объекта Abscissa * до объекта Abscissa_Quad *. Я добился этого, используя шаблонный класс в сочетании с не виртуальным шаблоном интерфейса.

Interpolator_Template.hpp

template< class Abscissa_Derived >
class Interpolator_Template : public Interpolator {

public:

/**
* Implements Interpolator::eval_det_J.
*/
double
eval_det_J(
const Abscissa * abscissa ) const;

protected:

/**
* Implemented by Interpolator_Quad_04 in this example.
*/
virtual double
eval_det_J(
const Abscissa_Derived * abscissa ) const = 0;

private:

Abscissa_Derived *
eval_abscissa(
const Abscissa * abscissa ) const;
};

Interpolator_Template.cpp

template< class Abscissa_Derived >
double
Interpolator_Template< Abscissa_Derived >::eval_det_J(
const Abscissa * abscissa ) const
{
Abscissa_Derived * abscissa_type = this->eval_abscissa( abscissa );

double det_J = this->eval_det_J( abscissa_type );

return det_J;
}

template< class Abscissa_Derived >
Abscissa_Derived *
Interpolator_Template< Abscissa_Derived >::eval_abscissa(
const Abscissa * abscissa ) const
{
// Dynamic cast occurs here.
// I will include some check later to check for nullptr.
return dynamic_cast< Abscissa_Derived * >( abscissa )
}

Я уверен, что этот код содержит ошибки, потому что мне нужно было копировать и вставлять только то, что я считал необходимым, чтобы донести свою мысль, а также выполнить изменения. Я надеюсь, однако, что моя идея проходит правильно.

Я знаю, что даункинг обычно является запахом кода, поэтому прежде чем я начал внедрять интеграторы и интерполяторы для всех геометрических форм в конечных элементах, я хотел бы узнать ваше мнение.

Это последний шаблон проектирования, который я реализовал. Я объясню другие проекты, которые я попытался ниже; однако, вы можете пропустить чтение этого раздела.

  • Шаблон проектирования с двойной диспетчеризацией (в частности, шаблон посетителя), в котором производным является интегратор, а не интерполятор. Например, у меня был Integrator_Quad_04, а не Interpolator_Quad_04. Интегратор_Quad_04 имел переменную-член Abscissa_Quad, так как абсциссы больше не были получены.

    class Integrator_Quad_04 final : public Integrator {
    
    private:
    
    std::vector< Abscissa_Quad * > _abscissae;
    
    public:
    
    double
    eval_det_J(
    const std::size_t &  index,
    const Interpolator * interpolator ) const
    {
    // The interpolator acts as the visitor.
    interpolator->eval_det_J( _abscissa[index] );
    }
    }
    
    /// Abscissa_Quad is no longer derived from Abscissa.
    
    class Abscissa_Quad {
    
    public:
    
    const double xi;
    
    const double eta;
    };
    

    Затем интерполятор становится посетителем класса интегратора и обращается к его переменной-члену _abscissae. Я решил не идти по этому проекту, потому что тогда интерполятор должен был бы быть реализован на основе операций, а не формы.

    class Interpolator {
    
    // ...
    
    };
    
    class Eval_Det_J : public Interpolator {
    
    double
    eval_det_J(
    const Abscissa_Quad * abscissa ) const;
    
    double
    eval_det_J(
    const Abscissa_Tria * abscissa ) const;
    };
    
  • Я пытался сделать что-то с множественной диспетчеризацией, но количество функций, необходимых для всех форм, росло довольно быстро.

  • Несколько вариантов двойной отправки + шаблонов.

  • Я нашел текущий шаблон дизайна, который я использую здесь:

    Объектно-ориентированная задача проектирования, принцип подстановки Лискова

Как вы могли догадаться из кода, я компилирую с использованием C ++ 11.

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

Я хочу знать, думаете ли вы, что уныние — не плохое решение этой проблемы, или я что-то упускаю. Возможно, я не знаю шаблон проектирования, который решает эту проблему, или, возможно, моя архитектура полиморфизма неверна.

Любые отзывы приветствуются. Спасибо!

1

Решение

Я публикую это как ответ, потому что это слишком много для комментария, и, возможно, это поможет.

Вы могли бы сделать Integrator класс шаблона:

template<class Shape>
class Integrator {
typedef typename Shape::AbscissaType AbscissaType;
public:
void integrate() const {
for (const AbscissaType& abscissa : _abscissae) {
_intepolator->eval_det_J(abscissa);
}
}

template<class OtherShape>
Integrator<OtherShape> convert(/* maybe pass range of abscissae indices */) const {
// Abscissae classes must have converting constructors like AbscissaTria(const AbscissaQuad&);
std::vector<typename OtherShape::AbscissaType> newAbscissae(_abscissae.begin(), _abscissae.end());
// initialize resulting integrator with newAbscissae
}

Interpolator_Template<AbscissaType> _interpolator;
std::vector<AbscissaType> _abscissae;
};

Конкретные интеграторы наследуются от соответствующего базового шаблона, если он все еще необходим:

class Integrator_Quad_04 : public Integrator<Quad> {
};

Так что вам не нужна база Interpolator класс и eval_det_J является не виртуальной функцией, принимающей соответствующий тип абсцисс:

template<class AbscissaType>
class Interpolator_Template {
public:
double eval_det_J(const AbscissaType& abscissa) const;
}

я добавил Shape здесь, чтобы подчеркнуть, что тип абсцисс зависит от формы, и у вас могут быть другие вещи, которые также зависят от формы:

struct Tria {
typedef AbscissaTria AbscissaType;
static const size_t NUM_EDGES = 3; // just example
};

struct Quad {
typedef AbscissaQuad AbscissaType;
static const size_t NUM_EDGES = 4; // just example
};

Я думаю, что у вас уже есть такие классы в вашем коде.

Также обратите внимание, что вам также не нужна база Abscissa все классы абсцисс становятся независимыми.

РЕДАКТИРОВАТЬ: если вам нужно преобразовать абсциссы в другой тип, вы можете реализовать следующую функцию в Integrator учебный класс:

template<class OtherShape>
Integrator<OtherShape> convert(/* maybe pass range of abscissae indices */) const {
}

РЕДАКТИРОВАТЬ 2: Добавлен пример реализации convert в Integrator учебный класс.

0

Другие решения


По вопросам рекламы [email protected]