【问题标题】:Lazy initialization of a static member array of a template class模板类的静态成员数组的延迟初始化
【发布时间】:2012-12-04 14:55:03
【问题描述】:

我正在编写代码来执行带有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&lt;12&gt; 的系数的翻译单元,方法是

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&lt;legendre&lt;n&gt;&gt; 时,模板legendre&lt;n&gt; 会自动实例化(就是这种情况)。
  • legendre&lt;n&gt; 为某个编译时n 实例化时,我希望在main 之前的某个时间点调用上面的compute_legendre_coeffs,以便它填充xw 成员数组。 我如何做到这一点?

我知道必须先定义数组:

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

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

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

【问题讨论】:

  • 延迟初始化的最佳方法是将其填充到一个静态函数中,该函数在第一次调用时初始化,然后通过引用返回数组。 typedef const double (&amp;arr_ref)[n];static arr_ref get_x(){ static const double x[n] = {}; static char c = (init(x),'0'); return x; }.
  • 我看不出将l​​egendre 作为模板的意义。甚至高斯正交。与现场成员一起解决这个问题要容易得多。你有成千上万的 gaussian_quadratures,所以你负担不起课堂上的额外字段吗? (每个实例增加 2 个额外字节)
  • @BarnabasSzabolcs:关于我正在使用的特定编译器的实验证据表明,拥有n 和在编译时已知的数组,我的性能会更好。
  • 不是 100% 确定,但您似乎并不真正需要 之前 main 计算的系数,而是在第一次使用之前。对?如果是这种情况,您可以制作系数 private 并提供一个 static 访问器来确保数组的初始化

标签: c++ templates static-members


【解决方案1】:

将数组转换为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());

【讨论】:

  • 这是我想到的。不幸的是,xw 必须一起计算。
  • @AlexandreC。最好的解决方法是封装系数;见上文。
【解决方案2】:
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_initdo_init 进行成员的实际初始化。

注意事项:

取决于编译器,这可能不是线程安全的(即,并非所有 C++03 编译器都提供静态变量的线程安全初始化,这反过来意味着 do_init 可能会被多次并行调用,具体取决于关于可能或不存在问题的算法——即,如果do_init 将值计算并写入它们,则潜在的竞争条件是无关紧要的,因为最终结果将是相同的)。一些编译器提供了保证一次性执行的机制(我相信 boost 有这样的机制)。或者,根据您的域,您可以在启动线程之前准备系数。

在这种情况下,实际的数组不能是const,因为初始化需要在创建后进行。除了可能的微优化之外,这不应该是任何问题(即编译器不知道系数的值,因此它无法在编译时执行子表达式评估)。

【讨论】:

  • 我也喜欢这个,很简单。需要注意的是,用于自定义正交权重的简单线性数组(我也使用自定义规则而不是 laguerre&lt;n&gt;)需要更多的工作。
  • C++11 有std::call_once,这可能很有用。
  • @Xeo:在 C++11 中,局部静态变量的初始化已经保证是线程安全的,所以不需要手动滚动其他任何东西。
  • @David:我知道,但你可能想知道为什么当你可以使用static char c = (only_once(), 'x'); 实现相同的目标时,他们为什么要添加这个。
  • @Xeo:两个原因:提供保证更简单、更明智(许多人不会意识到初始化是潜在的竞争条件)并且已经在一些编译器中实现了。然后,我什至不确定(only_once(),'x') 的语义是否符合您的期望,第二次我希望它不会评估only_once(),而是评估'x' 和分配......请注意赋值是表达式的一部分,应该只计算一次。
【解决方案3】:

首先:你不能在编译时使用 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];

您可能注意到,xw 不再是 const 了 :( -- 您可以尝试让它们 const 再次隐藏到 private 并添加静态 getter 以供调用者使用...此外,您的内部数组将在运行时初始化,但main 函数之前(并且只有一次)...


或玩constexpr... 但似乎你需要重新设计你的初始化函数(不知何故)因为使用初始化列表它应该看起来像这样:

template <size_t n>
static double legendre<n>::x[n] = { calc_coeff_x<0>(), calc_coeff_x<1>(), calc_coeff_x<2>(), ... }

...如果没有专业化(以及广泛的宏使用),您肯定无法做到这一点。 但可能可变参数模板可能会有所帮助...需要了解有关您的功能和思考时间的更多详细信息:))

【讨论】:

  • 这很有趣。谢谢。所涉及的功能太复杂,无法与 constexpr 一起使用(而且我现在无法访问 constexpr:我被 MSVC 困住了)
  • 另外,你能解释一下这两个帮助类的必要性吗?
  • 另一件事:我必须在static_xtors 中声明助手和legendre&lt;n&gt; 中的数组吗?
  • 对 static_xtors 类添加了一些解释......希望这个想法变得更加清晰......
  • 什么样的问题??在我使用此代码的所有项目中,static_xtors 类始终位于单独的文件 static_xtors.hh 中,它永远不会导致任何问题......真的,在你的情况下它也不应该导致,我看不出它是怎么回事可以:您有一个未初始化的静态数组(将放置在可执行映像的 .bss 部分中)和初始化它们的代码(何时调用它并不重要,重要的是它会发生在 before 主要)
【解决方案4】:

也许您可以尝试将您的函数转换为初始化程序类模板,其参数将是您要初始化的类/结构。然后更改该类模板以包含初始化程序的常量实例。最后,让初始化器类的构造函数触发执行实际初始化的代码。

您或许还应该保护对初始化程序类的访问 [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 模板,在这里可以派上用场。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-04-04
    • 1970-01-01
    • 2016-03-10
    • 1970-01-01
    • 1970-01-01
    • 2011-03-14
    • 2011-01-21
    相关资源
    最近更新 更多