沿着模板类使用非虚接口向下转换
Downcasting using a non-virtual interface along a template class
我正在实现一个有限元代码。
<标题>问题描述在有限元方法中,我们需要一个积分器和一个插值器。积分器是对几何对象(如四边形、三角形等)进行数值积分的对象。积分器在几何对象内放置多个积分点或横坐标,然后使用插值器来近似这些积分点处的函数值。
例如,一个四边形对象可以使用一个包含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_04
Integrator
|
| 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_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。Integrator_Quad_04有一个Abscissa_Quad作为成员变量,因为abscissae不再被派生。
然后,内插器成为积分器类的访问者,并访问其_abscissae成员变量。我决定不采用这种设计,因为那样的话,内插器将不得不根据操作来实现,而不是形状。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; };
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; };
我试着做一些多调度的事情,但是所有形状所需的函数数量增长得非常快。
双调度+模板的多种变体。
我找到了我在这里使用的当前设计模式:
面向对象设计问题,Liskov替代原理
结论
正如您可能从代码中推断的那样,我正在使用c++ 11进行编译。
你可能想知道为什么我不简单地把积分器和插值器合并成一个类,答案是因为积分器可以在四边形的子域上操作。例如,我可以在四边形中引入一个虚构的三角形,并在三角形中放置积分点,但我仍然会使用四边形插值来近似三角形点内的解。当然,我需要在三角形和四边形坐标之间实现一些映射,但这是另一天的问题。
我想知道你是否认为向下投射不是解决这个问题的坏方法,或者我是否错过了什么。也许我没有解决这个问题的设计模式的知识,或者我的多态性体系结构是不正确的。
感谢任何反馈。谢谢!
标题>我把这篇文章作为一个答案,因为评论太多了,也许它会有所帮助。
你可以让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
,所有abscissae类都是独立的。
Integrator
类中实现以下功能:
template<class OtherShape>
Integrator<OtherShape> convert(/* maybe pass range of abscissae indices */) const {
}
编辑2:将convert
的示例实现添加到Integrator
类中。
- 从常量字符*、字符*参数到标准::字符串的直接转换接口
- 在构造函数处将类对象强制转换为接口始终返回 NULL
- 尝试向 COM 对象添加另一个接口时出现静态强制转换错误 C2440
- 将下一个(),hasNext()迭代界面转换为begin(),end()接口
- 如何将接口对象强制转换为特定的继承对象
- C++ 从接口和强制转换的多重继承
- 在 C# 中加载 COM 对象会引发异常"无法将类型'System.__ComObject'的 COM 对象强制转换为接口类型...",但C++或 VB 不会
- C++从接口类到子类的转换无效
- 如何从C类中告知/强制转换for C++接口的实例
- 从Const引用接口到派生类的转换
- 向上转换到超类或接口
- CLR 探查器:COM 样式的强制转换和从派生接口调用函数
- 具有扩展接口的派生类的集合.如何在没有动态强制转换的情况下访问派生接口
- 有没有办法将 COM 接口 GUID 转换为接口的字符串名称
- 使用接口包装库,而无需向下转换
- 如何为一个简单的转换例程设计一个接口
- opencv c与c++接口转换表
- 将类接口转换为类模板
- 沿着模板类使用非虚接口向下转换
- 静态转换接口类到内部引擎实现