我正在编写代码以n
点执行Gaussian integration,其中n
是编译时常量。
对于给定的n
,我知道如何计算横坐标和权重。对于每个不同的n
,必须从头开始计算。
现在,我在这些方面做了一些事情:
// Several structs like this one (laguerre, chebyshev, etc).
template <size_t n>
struct legendre
{
static const size_t size = n;
static const double x[n];
static const double w[n];
};
template <typename Rule, typename F>
double gauss_quadrature (F&& f)
{
double acc = 0;
for (size_t j = 0; j < Rule::size; j++)
acc += Rule::w[j] * f (Rule::x[j]);
return acc;
}
这样使用:
double i = gauss_quadrature<legendre<12>> (f);
现在,我可以通过
专门研究legendre<12>
系数的翻译单位
template <>
const legendre<12>::x[12] = { ... };
template <>
const legendre<12>::w[12] = { ... };
一切都很好,只要我只使用12分高斯 - 勒让德。
现在,我正在尝试不同数量的点,我知道如何生成权重和节点。我可以举例说明
void compute_legendre_coeffs (size_t n, double* w, double* x);
和:
gauss_quadrature<legendre<n>>
时,模板legendre<n>
会自动实例化(就是这种情况)。legendre<n>
实例化n
时,我希望在main之前的某个时刻调用上面的compute_legendre_coeffs
,以便填充x
{1}}和w
成员数组。我如何实现这一目标?我知道必须首先定义数组:
template <size_t n>
const double legendre<n>::x[n] = {};
template <size_t n>
const double legendre<n>::w[n] = {};
但我无法想出一种方法来初始化它们。任何人都有诀窍吗?
答案 0 :(得分:2)
将数组转换为std::array
:
#include <array>
template<int n> struct legendre {
static const std::array<double, n> x;
};
void compute_xs(int n, double *xs) {
...
}
template<int n> std::array<double, n> make_xs() {
std::array<double, n> xs;
compute_xs(n, xs.data());
return xs;
}
template<int n> const std::array<double, n> legendre<n>::x = make_xs<n>();
这确实意味着分别计算x
和w
系数,但如果效率较低,则会有变通方法,例如:
template<int n> struct legendre_coeffs {
std::array<double, n> x, w;
legendre_coeffs(): x(), w() { compute_legendre_coeffs(n, w.data(), x.data()); }
};
template<int n> struct legendre {
static const legendre_coeffs coeffs;
static const double (&x)[n], (&w)[n];
};
template<int n> const legendre_coeffs legendre<n>::coeffs;
template<int n> const double (&legendre<n>::x)[n]
= *reinterpret_cast<const double (*)[n]>(legendre<n>::coeffs::x.data());
template<int n> const double (&legendre<n>::w)[n]
= *reinterpret_cast<const double (*)[n]>(legendre<n>::coeffs::w.data());
答案 1 :(得分:1)
首先:你不能在编译时使用C ++ 03完全初始化(是的,按设计!) - 唯一的方法就是使用C ++模板,但你不能传递double作为模板参数。
随着C ++ 11越来越好 - 你可以使用constexpr
,但前提是你的compute_legendre_coeffs()
足够简单。
或者,当需要通过类声明的事实采取一些操作时,我会使用一个技巧 - 例如,在某处注册smth ...以通过某些库提供序列化功能或像这样提供smth。
您可以使用 static constructors idiom 来初始化该数组......出于类似原因,我使用以下代码:
template <
typename Derived
, typename Target = Derived
>
class static_xtors
{
// This class will actually call your static init methods...
struct helper
{
helper()
{
Target::static_ctor();
}
~helper()
{
Target::static_dtor();
}
};
// ... because your derived class would inherit this member from static_xtor base
static helper s_helper;
// The rest is needed to force compiler to instantiate everything required stuff
// w/o eliminate as unused...
template <void(*)()>
struct helper2 {};
static void use_helper()
{
(void)s_helper;
}
helper2<&static_xtors::use_helper> s_helper2;
virtual void use_helper2()
{
(void)s_helper2;
}
public:
/// this is not required for your case... only if later you'll have
/// a hierarchy w/ virtuals
virtual ~static_xtors() {}
};
template <
typename Derived
, typename Target
>
typename static_xtors<Derived, Target>::helper
static_xtors<Derived, Target>::s_helper;
然后你必须继承static_xtors类,并实现两个静态方法:void static_ctor()
- 这将初始化你的数组,并且为空(在你的情况下)void static_dtor()
... I.e。像这样的人:
template <size_t n>
struct legendre : public static_xtors<legendre<n>>
{
static const size_t size = n;
static double x[n];
static double w[n];
static void static_ctor()
{
compute_legendre_coeffs(n, x, w);
}
static void static_dtor()
{
// nothing to do
}
};
template <size_t n>
static double legendre<n>::x[n];
template <size_t n>
static double legendre<n>::w[n];
您可能已经注意到,x
和w
不再是const :( - 您可能会尝试让const
再次隐藏到private
并添加静态getter由调用者使用...此外,您的内部数组将在运行时初始化,但之前 main
函数(只需一次)......
或播放w / constexpr
...但似乎您需要重新设计初始化函数(以某种方式),因为使用初始化列表它应该如下所示:
template <size_t n>
static double legendre<n>::x[n] = { calc_coeff_x<0>(), calc_coeff_x<1>(), calc_coeff_x<2>(), ... }
...绝对不能没有专业化(以及广泛的宏用法)。 但是变量模板可能有所帮助......需要了解有关您的功能和时间的更多详细信息:))
答案 2 :(得分:1)
template <size_t n>
class legendre
{
public:
static const size_t size = n;
static const double (&getX())[n] {
init();
return x;
}
static const double (&getW())[n] {
init();
return x;
}
private:
static double x[n];
static double w[n];
static void init() {
static bool _ = do_init(x,y);
}
static bool do_init( double *x, double *y ) {
// do the computation here, use local vars x, y
return true;
}
};
template <size_t n>
double legendre<n>::x[n];
template <size_t n>
double legendre<n>::w[n];
通过提供访问者,您可以控制您班级的入口点。访问器调度到init
函数,该函数使用本地静态变量的初始化在程序生存期中仅调用do_init
一次。 do_init
执行成员的实际初始化。
注意:
根据编译器的不同,这可能不是线程安全的(即并非所有C ++ 03编译器都提供静态变量的线程安全初始化,这反过来意味着并行调用do_init
可能不止一次,取决于可能或不成为问题的算法 - 如果do_init
计算值并且只是写它们,则潜在的竞争条件是无关紧要的,因为净结果将是相同的)。一些编译器提供了保证一次性执行的机制(我相信boost有这样的机制)。或者,根据您的域,您可以在启动线程之前填充系数。
在这种情况下,实际的数组不能是const
,因为初始化需要在创建之后发生。除了可能的微优化之外,这不应该是一个问题(即编译器不知道系数的值,因此它不能在编译时执行子表达式评估。)
答案 3 :(得分:0)
也许您可以尝试将函数转换为初始化类模板,其参数将是您要初始化的类/结构。然后更改该类模板以包含初始化程序的常量实例。最后,让初始化类的构造函数触发执行实际初始化的代码。
您也应该保护对初始化程序类的访问[1],以便初始化不会发生多次。
正如您所看到的,这个想法是使用类实例获取其构造函数代码的事实,模板实例将初始化其常量数据。
下面是一个可能(简单)的实现,没有模板:
struct legendre_init {
legendre_init(){
compute_legendre_coeffs (T::n, T::w, T::x);
}
};
template <size_t n>
struct legendre
{
typedef legendre<n> self_type;
static const size_t size = n;
static const double x[n];
static const double w[n];
static const legendre_init _l;
};
这是对它的另一种看法,这次直接在结构中进行初始化:
template <class T>
class T_init {
public:
T_init(){
T::_init();
}
};
template <size_t n>
struct legendre
{
typedef legendre<n> self_type;
static const size_t size = n;
static const double x[n];
static const double w[n];
static const T_init<self_type> _f;
static void _init(){
compute_legendre_coeffs (self_type::n, self_type::w, self_type::x);
}
};
此解决方案的有趣特性是T_init
常量实例不应占用T
结构中的任何空间。初始化逻辑与需要它的类捆绑在一起,T_init
模板仅自动启用它。
[1] Xeo提到了std :: call_once模板,它可以在这里派上用场。