懒惰初始化模板类的静态成员数组

时间:2012-12-04 14:55:04

标签: c++ templates static-members

我正在编写代码以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] = {};

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

4 个答案:

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

这确实意味着分别计算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());

答案 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];

您可能已经注意到,xw不再是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模板,它可以在这里派上用场。