Inisialisasi lambat dari array anggota statis dari kelas templat

Saya menulis kode untuk melakukan integrasi Gaussian dengan n poin, di mana n adalah konstanta waktu kompilasi.

Untuk n tertentu, saya tahu cara menghitung absis dan bobot. Perhitungan harus dilakukan dari awal untuk setiap n yang berbeda.

Sekarang, saya melakukan sesuatu seperti ini:

// 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;
}

untuk digunakan seperti ini:

double i = gauss_quadrature<legendre<12>> (f);

Sekarang, saya dapat mengkhususkan diri pada satuan terjemahan koefisien untuk legendre<12>, dengan melakukan

template <>
const legendre<12>::x[12] = { ... };

template <>
const legendre<12>::w[12] = { ... };

dan semuanya baik-baik saja, selama saya hanya menggunakan Gauss-Legendre 12 poin.

Sekarang, saya bereksperimen dengan jumlah poin yang berbeda, dan saya tahu cara menghasilkan bobot dan node. Misalnya, saya dapat memberikan rutinitas

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

Dan :

  • Saat saya memanggil gauss_quadrature<legendre<n>>, templat legendre<n> secara otomatis dibuat (inilah kasusnya).
  • Ketika legendre<n> dipakai untuk beberapa waktu kompilasi n, saya ingin compute_legendre_coeffs di atas dipanggil di beberapa titik sebelum main sehingga memenuhi array anggota x dan w. Bagaimana cara mencapainya?

Saya tahu harus mendefinisikan array terlebih dahulu:

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

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

tapi saya tidak bisa menemukan metode untuk menginisialisasinya. Adakah yang punya trik untuk melakukannya?


person Alexandre C.    schedule 04.12.2012    source sumber
comment
Cara terbaik untuk menginisialisasi sesuatu dengan lambat adalah dengan memasukkannya ke dalam fungsi statis yang diinisialisasi pada panggilan pertama dan mengembalikan array dengan referensi setelah itu. typedef const double (&arr_ref)[n]; dan static arr_ref get_x(){ static const double x[n] = {}; static char c = (init(x),'0'); return x; }.   -  person Xeo    schedule 04.12.2012
comment
Saya tidak mengerti gunanya menjadikan legenda sebagai templat. Bahkan gauss_quadrature. Jauh lebih mudah untuk menyelesaikan masalah ini dengan anggota lapangan. Apakah Anda memiliki ribuan gaussian_quadratures sehingga Anda tidak mampu membeli kolom tambahan di kelas? (yang menghasilkan 2 byte tambahan per instance)   -  person Barney Szabolcs    schedule 04.12.2012
comment
@BarnabasSzabolcs: Bukti eksperimental pada kompiler tertentu yang saya gunakan menunjukkan bahwa kinerja saya lebih baik jika memiliki n dan array yang diketahui pada waktu kompilasi.   -  person Alexandre C.    schedule 04.12.2012
comment
Tidak 100% yakin, namun tampaknya Anda tidak benar-benar memerlukan koefisien yang dihitung sebelum main, tetapi tepat sebelum penggunaan pertama. Benar? Jika demikian, Anda dapat membuat koefisien private dan menawarkan pengakses static yang memastikan inisialisasi array   -  person David Rodríguez - dribeas    schedule 04.12.2012


Jawaban (4)


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

Dengan menyediakan pengakses, Anda mendapatkan kendali atas titik masuk ke kelas Anda. Pengakses mengirimkan ke fungsi init yang menggunakan inisialisasi variabel statis lokal untuk memanggil do_init hanya sekali seumur hidup program. do_init melakukan inisialisasi anggota yang sebenarnya.

Catatan:

Tergantung pada kompilernya, ini mungkin tidak aman untuk thread (yaitu tidak semua kompiler C++03 menyediakan inisialisasi variabel statis yang aman untuk thread, yang pada gilirannya berarti bahwa do_init mungkin dipanggil lebih dari sekali secara paralel, tergantung pada algoritma yang mungkin atau tidak menjadi masalah --yaitu jika do_init menghitung nilai-nilai tersebut dan hanya menuliskannya, kondisi balapan potensial tidak relevan karena hasil akhirnya akan sama). Beberapa kompiler menawarkan mekanisme untuk menjamin eksekusi satu kali (saya yakin boost memiliki mekanisme seperti itu). Alternatifnya, bergantung pada domain Anda, Anda mungkin dapat memprioritaskan koefisien sebelum memulai thread.

Array sebenarnya tidak boleh const dalam kasus ini, karena inisialisasi harus dilakukan setelah dibuat. Hal ini seharusnya tidak menjadi masalah selain kemungkinan optimasi mikro (yaitu kompiler tidak mengetahui nilai koefisien, sehingga tidak dapat melakukan evaluasi sub-ekspresi pada waktu kompilasi).

person David Rodríguez - dribeas    schedule 04.12.2012
comment
Aku juga suka yang ini, sederhana. Peringatannya adalah array baris sederhana untuk bobot kuadratur khusus (saya juga menggunakan aturan khusus alih-alih laguerre<n>) memerlukan lebih banyak pekerjaan. - person Alexandre C.; 04.12.2012
comment
C++11 memiliki std::call_once, yang mungkin berguna. - person Xeo; 04.12.2012
comment
@Xeo: Dalam C++11 inisialisasi variabel statis lokal sudah dijamin aman untuk thread, jadi tidak perlu menggulung apa pun secara manual. - person David Rodríguez - dribeas; 04.12.2012
comment
@David: Saya tahu, tetapi Anda mungkin bertanya-tanya mengapa mereka menambahkan ini ketika Anda dapat mencapai hal yang sama dengan static char c = (only_once(), 'x');. - person Xeo; 04.12.2012
comment
@Xeo: Dua alasan: lebih sederhana, dan masuk akal untuk memberikan jaminan (banyak orang tidak akan menyadari bahwa inisialisasi adalah kondisi balapan potensial) dan sudah diterapkan di beberapa kompiler. Kemudian, saya bahkan tidak yakin bahwa semantik (only_once(),'x') adalah apa yang Anda harapkan, untuk kedua kalinya saya berharap itu tidak mengevaluasi only_once(), tetapi mengevaluasi 'x' dan penugasan... perhatikan bahwa penugasan itu adalah bagian dari ekspresi itu hanya boleh dievaluasi satu kali. - person David Rodríguez - dribeas; 04.12.2012

Ubah array menjadi 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>();

Ini berarti menghitung koefisien x dan w secara terpisah, meskipun ada solusi jika hal ini kurang efisien, misalnya:

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());
person ecatmur    schedule 04.12.2012
comment
Ini adalah sesuatu yang saya pikirkan. Sayangnya, x dan w harus dihitung bersama. - person Alexandre C.; 04.12.2012
comment
@AlexandreC. solusi terbaik adalah dengan merangkum koefisien; Lihat di atas. - person ecatmur; 04.12.2012

Pertama-tama: Anda tidak dapat melakukan inisialisasi sepenuhnya pada waktu kompilasi menggunakan C++03 (ya, memang disengaja!) -- satu-satunya cara untuk melakukannya adalah dengan menggunakan templat C++, tetapi Anda tidak akan dapat melewati double sebagai parameter templat.

Segalanya menjadi lebih baik dengan C++11 -- Anda dapat menggunakan constexpr tetapi hanya jika compute_legendre_coeffs() Anda cukup sepele.

Atau ada satu trik yang saya gunakan ketika perlu mengambil beberapa tindakan berdasarkan deklarasi kelas -- misalnya, mendaftarkan sesuatu di suatu tempat... untuk menyediakan kemampuan serialisasi melalui beberapa perpustakaan atau sesuatu seperti ini.

Anda dapat menggunakan idiom konstruktor statis untuk menginisialisasi array itu... Untuk alasan serupa saya menggunakan kode berikut:

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;

maka Anda harus mewarisi kelas static_xtors, dan mengimplementasikan dua metode statis: void static_ctor() -- yang akan menginisialisasi array Anda, dan mengosongkan (dalam kasus Anda) void static_dtor()... Yaitu. sesuatu seperti ini:

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

Seperti yang mungkin Anda perhatikan, x dan w bukan const lagi :( -- Anda dapat mencoba membuatnya const lagi bersembunyi di private dan menambahkan pengambil statis untuk digunakan oleh pemanggil... Selain itu, array internal Anda akan diinisialisasi saat runtime, tapi sebelum fungsi main (dan hanya sekali)...


atau mainkan dengan constexpr... tetapi sepertinya Anda harus mendesain ulang fungsi penginisialisasi Anda (entah bagaimana) karena menggunakan daftar inisialisasi akan terlihat seperti ini:

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

... dan tentunya Anda tidak dapat melakukannya tanpa spesialisasi (dan penggunaan makro yang ekstensif). Tapi mungkin templat variadik dapat membantu... perlu mengetahui lebih detail tentang fungsi Anda dan waktu untuk berpikir :))

person zaufi    schedule 04.12.2012
comment
Ini sangat menarik. Terima kasih. Namun fungsi yang terlibat terlalu rumit untuk digunakan dengan constexpr (dan saya tidak memiliki akses ke constexpr sekarang: Saya terjebak dengan MSVC) - person Alexandre C.; 04.12.2012
comment
Juga, bisakah Anda menjelaskan sedikit apa pentingnya kedua kelas pembantu tersebut? - person Alexandre C.; 04.12.2012
comment
Hal lain: apakah ada urutan tertentu di mana saya harus mendeklarasikan helper di static_xtors dan array di legendre<n> ? - person Alexandre C.; 04.12.2012
comment
menambahkan beberapa penjelasan ke kelas static_xtors... semoga idenya menjadi lebih jelas... - person zaufi; 04.12.2012
comment
masalah apa?? Di semua proyek di mana saya menggunakan kode ini, kelas static_xtors selalu berada di file terpisah static_xtors.hh dan tidak pernah menimbulkan masalah... Sungguh, dalam kasus Anda, kelas tersebut seharusnya tidak mengarah juga, dan saya tidak tahu bagaimana caranya bisa: Anda memiliki array statis yang belum diinisialisasi (akan ditempatkan di bagian .bss dari gambar yang dapat dieksekusi) dan kode untuk menginisialisasinya (tidak masalah kapan akan dipanggil, yang penting hanya itu akan terjadi sebelum utama) - person zaufi; 04.12.2012
comment
Saya akan melakukan tes tambahan besok -- Saya menggunakan kode Anda hampir apa adanya dan array tidak diinisialisasi. - person Alexandre C.; 05.12.2012

Mungkin Anda dapat mencoba mengubah fungsi Anda menjadi templat kelas penginisialisasi, yang parameternya adalah kelas/struktur yang ingin Anda inisialisasi. Kemudian ubah templat kelas itu untuk menyertakan instance penginisialisasi yang konstan. Terakhir, minta konstruktor kelas penginisialisasi memicu kode untuk melakukan inisialisasi sebenarnya.

Anda mungkin juga harus melindungi akses [1] ke kelas penginisialisasi sehingga inisialisasi tidak terjadi lebih dari sekali.

Idenya, seperti yang Anda lihat, adalah dengan menggunakan fakta bahwa instance kelas memanggil kode konstruktornya, instance template menginisialisasi data konstannya.

Di bawah ini adalah implementasi yang mungkin (dan sederhana), tanpa template:

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;
};

Berikut ini cara lain, kali ini menempatkan inisialisasi di struct secara langsung:

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

Karakteristik menarik dari solusi ini adalah instance konstan T_init tidak boleh mengambil ruang apa pun di dalam T struct. Logika inisialisasi digabungkan dengan kelas yang memerlukannya, dan templat T_init hanya mengaktifkannya secara otomatis.

[1] Xeo menyebutkan template std::call_once, yang mungkin berguna di sini.

person didierc    schedule 04.12.2012