4

我正在编写代码来执行与点的高斯积分n,其中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 点 Gauss-Legendre。

现在,我正在尝试不同数量的点,并且我知道如何生成权重和节点。例如,我可以提供一个例程

void compute_legendre_coeffs (size_t n, double* w, double* x);

和 :

  • 当我调用gauss_quadrature<legendre<n>>时,模板legendre<n>会自动实例化(就是这种情况)。
  • legendre<n>为某些 compile-time 实例化时n,我希望compute_legendre_coeffs在 main 之前的某个时间点调用上面的内容,以便它填充xw成员数组。我该如何做到这一点?

我知道必须先定义数组:

template <size_t n>
const double legendre<n>::x[n] = {};

template <size_t n>
const double legendre<n>::w[n] = {};

但我想不出一种方法来初始化它们。任何人都有这样做的诀窍吗?

4

4 回答 4

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>();

这确实意味着分别计算xw系数,但如果效率较低,则有一些解决方法,例如:

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());
于 2012-12-04T15:17:31.383 回答
1

首先:你不能在编译时使用 C++03 完全初始化(是的,设计使然!)——唯一的方法是使用 C++ 模板,但是你不能传递一个 double作为模板参数。

使用 C++11 情况会变得更好——你可以使用constexpr,但前提是你compute_legendre_coeffs()的足够微不足道。

或者,当需要根据类声明的事实采取一些行动时,我会使用一种技巧——例如,在某处注册 smth ......以通过某些库或类似的 smth 提供序列化功能。

您可以使用静态构造函数来初始化该数组...出于同样的原因,我使用以下代码:

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()......就像这样:

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>(), ... }

...而且绝对不能在没有专业化(以及广泛使用宏)的情况下做到这一点。但可能可变参数模板可能会有所帮助...需要了解有关您的功能和思考时间的更多详细信息:))

于 2012-12-04T15:44:04.027 回答
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在这种情况下,因为初始化需要在创建后进行。除了可能的微优化之外,这不应该是任何问题(即编译器不知道系数的值,因此它无法在编译时执行子表达式评估)。

于 2012-12-04T16:07:32.467 回答
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 模板,在这里可以派上用场。

于 2012-12-04T15:17:07.577 回答