การเริ่มต้น Lazy ของอาร์เรย์สมาชิกแบบคงที่ของคลาสเทมเพลต

ฉันกำลังเขียนโค้ดเพื่อดำเนินการการรวมแบบเกาส์เซียน ด้วย n พอยต์ โดยที่ n คือค่าคงที่ของเวลาคอมไพล์

สำหรับ n ที่ระบุ ฉันรู้วิธีคำนวณ abscissas และน้ำหนัก การคำนวณจะต้องทำตั้งแต่เริ่มต้นสำหรับ 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] = { ... };

และทุกอย่างเรียบร้อยดี ตราบใดที่ฉันใช้ Gauss-Legendre 12 แต้มเท่านั้น

ตอนนี้ ฉันกำลังทดลองกับจำนวนจุดที่แตกต่างกัน และฉันรู้วิธีสร้างน้ำหนักและโหนด เช่น ฉันสามารถจัดทำกิจวัตรประจำวันได้

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

และ :

  • เมื่อฉันโทร gauss_quadrature<legendre<n>> เทมเพลต legendre<n> จะถูกสร้างอินสแตนซ์โดยอัตโนมัติ (ในกรณีนี้)
  • เมื่อ legendre<n> ถูกสร้างอินสแตนซ์สำหรับเวลาคอมไพล์ n บางเวลา ฉันต้องการให้เรียก compute_legendre_coeffs ข้างต้นที่จุดใดจุดหนึ่งก่อน main เพื่อให้มันเติมเต็มอาร์เรย์สมาชิก x และ w ฉันจะบรรลุสิ่งนี้ได้อย่างไร

ฉันรู้ว่าต้องกำหนดอาร์เรย์ก่อน:

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

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

แต่ฉันไม่สามารถคิดวิธีเริ่มต้นได้ ใครมีเคล็ดลับในการทำเช่นนั้น?


person Alexandre C.    schedule 04.12.2012    source แหล่งที่มา
comment
วิธีที่ดีที่สุดในการเริ่มต้นบางสิ่งแบบ Lazy คือการยัดมันลงในฟังก์ชันคงที่ซึ่งจะเริ่มต้นเมื่อมีการโทรครั้งแรกและส่งกลับอาร์เรย์โดยการอ้างอิงหลังจากนั้น typedef const double (&arr_ref)[n]; และ 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
ฉันไม่เห็นประเด็นในการสร้างเทมเพลตตำนาน แม้แต่ gauss_quadrature การแก้ปัญหานี้กับสมาชิกภาคสนามทำได้ง่ายกว่ามาก คุณมี gaussian_quadratures หลายพันรูป คุณจึงไม่มีเงินจ่ายค่าฟิลด์พิเศษในชั้นเรียนใช่หรือไม่ (ซึ่งครอบตัดเพิ่ม 2 ไบต์ต่ออินสแตนซ์)   -  person Barney Szabolcs    schedule 04.12.2012
comment
@BarnabasSzabolcs: หลักฐานการทดลองเกี่ยวกับคอมไพเลอร์เฉพาะที่ฉันใช้แสดงให้เห็นว่าฉันมีประสิทธิภาพดีกว่าการมี n และอาร์เรย์ที่รู้จักในเวลาคอมไพล์   -  person Alexandre C.    schedule 04.12.2012
comment
ไม่แน่ใจ 100% แต่ดูเหมือนว่าคุณไม่จำเป็นต้องมีสัมประสิทธิ์ที่คำนวณ ก่อน main จริงๆ แต่ก่อนการใช้งานครั้งแรก ขวา? หากเป็นกรณีนี้ คุณสามารถสร้างสัมประสิทธิ์ private และเสนอตัวเข้าถึง static เพื่อให้แน่ใจว่าอาร์เรย์จะเริ่มต้นได้   -  person David Rodríguez - dribeas    schedule 04.12.2012


คำตอบ (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];

การให้ accessor ทำให้คุณสามารถควบคุมจุดเข้าสู่ชั้นเรียนของคุณได้ ตัวเข้าถึงส่งไปยังฟังก์ชัน init ที่ใช้การเริ่มต้นของตัวแปรสแตติกในเครื่องเพื่อเรียก do_init เพียงครั้งเดียวตลอดอายุการใช้งานของโปรแกรม do_init ทำการกำหนดค่าเริ่มต้นจริงของสมาชิก

หมายเหตุ:

ขึ้นอยู่กับคอมไพลเลอร์ สิ่งนี้อาจไม่ปลอดภัยสำหรับเธรด (เช่น คอมไพเลอร์ C ++ 03 ไม่ใช่ทั้งหมดที่มีการเตรียมใช้งานตัวแปรสแตติกที่ปลอดภัยสำหรับเธรด ซึ่งหมายความว่า do_init อาจถูกเรียกใช้แบบขนานมากกว่าหนึ่งครั้ง ขึ้นอยู่กับอัลกอริทึมที่อาจ หรือไม่เป็นปัญหา --เช่น หาก do_init คำนวณค่าต่างๆ กันและเพียงแค่เขียนค่าเหล่านั้น สภาพการแข่งขันที่อาจเกิดขึ้นจะไม่เกี่ยวข้องเนื่องจากผลลัพธ์สุทธิจะเหมือนกัน) คอมไพเลอร์บางตัวเสนอกลไกเพื่อรับประกันการดำเนินการเพียงครั้งเดียว (ฉันเชื่อว่าบูสต์มีกลไกดังกล่าว) หรืออาจ เตรียม ค่าสัมประสิทธิ์ก่อนที่จะเริ่มเธรด ทั้งนี้ขึ้นอยู่กับโดเมนของคุณ

อาร์เรย์ที่แท้จริงไม่สามารถเป็น const ในกรณีนี้ เนื่องจากการเริ่มต้นจะต้องเกิดขึ้นหลังจากสร้างขึ้นแล้ว นั่นไม่ควรเป็นปัญหาสำหรับสิ่งอื่นใดนอกจากการเพิ่มประสิทธิภาพระดับไมโครที่เป็นไปได้ (เช่น คอมไพลเลอร์ไม่ทราบเกี่ยวกับค่าของสัมประสิทธิ์ ดังนั้นจึงไม่สามารถทำการประเมินนิพจน์ย่อยในเวลาคอมไพล์ได้)

person David Rodríguez - dribeas    schedule 04.12.2012
comment
ฉันก็ชอบอันนี้เหมือนกัน มันเรียบง่าย Caveat คือความเรียบง่ายในอาร์เรย์แบบเส้นสำหรับน้ำหนักการสร้างพื้นที่สี่เหลี่ยมจัตุรัสที่กำหนดเอง (ฉันยังใช้กฎที่กำหนดเองแทน laguerre<n>) ต้องทำงานเพิ่มอีกเล็กน้อย - person Alexandre C.; 04.12.2012
comment
C++11 มี std::call_once ซึ่งอาจมีประโยชน์ - person Xeo; 04.12.2012
comment
@Xeo: ในการเริ่มต้น C ++ 11 ของตัวแปรคงที่ในเครื่องได้รับการรับรองแล้วว่าปลอดภัยสำหรับเธรดแล้ว ดังนั้นจึงไม่จำเป็นต้องม้วนสิ่งอื่นใดด้วยตนเอง - person David Rodríguez - dribeas; 04.12.2012
comment
@David: ฉันรู้ แต่คุณอาจสงสัยว่าทำไมพวกเขาถึงเพิ่มสิ่งนี้เมื่อคุณสามารถบรรลุผลเช่นเดียวกันกับ static char c = (only_once(), 'x'); - person Xeo; 04.12.2012
comment
@Xeo: สองเหตุผล: มันง่ายกว่าและสมเหตุสมผลที่จะให้การรับประกัน (หลายคนไม่รู้ว่าการเริ่มต้นนั้นเป็นสภาวะการแข่งขันที่อาจเกิดขึ้น) และได้ถูกนำไปใช้แล้วในคอมไพเลอร์บางตัว จากนั้น ฉันไม่แน่ใจด้วยซ้ำว่าความหมายของ (only_once(),'x') เป็นสิ่งที่คุณคาดหวัง ครั้งที่สองฉันคาดหวังว่าจะไม่ประเมิน only_once() แต่ประเมิน 'x' และการมอบหมาย... โปรดทราบว่าการมอบหมายนั้นเป็นส่วนหนึ่งของนิพจน์ ที่ควรได้รับการประเมินเพียงครั้งเดียว - person David Rodríguez - dribeas; 04.12.2012

แปลงอาร์เรย์เป็น 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());
person ecatmur    schedule 04.12.2012
comment
นี่คือสิ่งที่ฉันคิดเกี่ยวกับ ขออภัย x และ w ต้องคำนวณร่วมกัน - person Alexandre C.; 04.12.2012
comment
@AlexandreC. วิธีแก้ปัญหาที่ดีที่สุดคือการห่อหุ้มค่าสัมประสิทธิ์ ดูด้านบน. - person ecatmur; 04.12.2012

ก่อนอื่น: คุณไม่สามารถเริ่มต้นได้อย่างสมบูรณ์ในเวลาคอมไพล์โดยใช้ C++03 (ใช่แล้ว โดยการออกแบบ!) -- วิธีเดียวที่จะทำได้คือใช้เทมเพลต C++ แต่คุณจะไม่สามารถผ่านสองเท่าได้ เป็นพารามิเตอร์เทมเพลต

สิ่งต่างๆ จะดีขึ้นด้วย 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 อีกครั้ง และเพิ่ม getters แบบคงที่เพื่อให้ผู้โทรใช้... นอกจากนี้ อาร์เรย์ภายในของคุณจะถูกเตรียมใช้งานเมื่อรันไทม์ แต่ ก่อน ฟังก์ชัน main (และเพียงครั้งเดียว)...


หรือเล่นด้วย constexpr... แต่ดูเหมือนว่าคุณจะต้องออกแบบฟังก์ชันตัวเริ่มต้นใหม่ (อย่างใด) เพราะการใช้รายการเริ่มต้นควรมีลักษณะดังนี้:

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

... และแน่นอนว่าคุณไม่สามารถทำได้หากไม่มีความเชี่ยวชาญ (และการใช้มาโครอย่างกว้างขวาง) แต่เทมเพลตที่หลากหลายอาจช่วยได้... จำเป็นต้องทราบรายละเอียดเพิ่มเติมเกี่ยวกับฟังก์ชันและเวลาในการคิดของคุณ :))

person zaufi    schedule 04.12.2012
comment
สิ่งนี้น่าสนใจมาก ขอบคุณ. ฟังก์ชั่นที่เกี่ยวข้องนั้นซับซ้อนเกินกว่าจะใช้กับ constexpr ได้ (และฉันไม่มีสิทธิ์เข้าถึง constexpr ในขณะนี้: ฉันติดอยู่กับ MSVC) - person Alexandre C.; 04.12.2012
comment
นอกจากนี้ คุณช่วยอธิบายหน่อยได้ไหมว่าอะไรคือความจำเป็นสำหรับคลาสผู้ช่วยทั้งสองคลาส? - person Alexandre C.; 04.12.2012
comment
อีกประการหนึ่ง: มีลำดับเฉพาะที่ฉันต้องประกาศตัวช่วยใน static_xtors และอาร์เรย์ใน legendre<n> หรือไม่? - person Alexandre C.; 04.12.2012
comment
เพิ่มคำอธิบายบางอย่างให้กับคลาส static_xtors... หวังว่าแนวคิดจะชัดเจนยิ่งขึ้น... - person zaufi; 04.12.2012
comment
มีปัญหาอะไร?? ในทุกโปรเจ็กต์ที่ฉันใช้โค้ดนี้ คลาส static_xtors จะอยู่ในไฟล์แยกต่างหาก static_xtors.hh เสมอ และไม่เคยนำไปสู่ปัญหาใด ๆ... จริงๆ แล้ว ในกรณีของคุณ มันไม่ควรนำไปสู่เช่นกัน และฉันไม่เห็นวิธีเลย ทำได้: คุณมีอาร์เรย์สแตติกที่ไม่ได้เตรียมใช้งาน (จะถูกวางไว้ในส่วน .bss ของอิมเมจที่ปฏิบัติการได้) และโค้ดสำหรับเตรียมใช้งาน (ไม่สำคัญว่าจะถูกเรียกเมื่อใด สำคัญเพียงว่ามันจะเกิดขึ้น ก่อน) หลัก) - person zaufi; 04.12.2012
comment
พรุ่งนี้ฉันจะทำการทดสอบเพิ่มเติม ฉันใช้โค้ดของคุณเกือบจะเหมือนเดิมและอาร์เรย์ไม่ได้เริ่มต้นใช้งาน - person Alexandre C.; 05.12.2012

บางทีคุณอาจลองเปลี่ยนฟังก์ชันของคุณให้เป็นเทมเพลตคลาส Initializer ซึ่งพารามิเตอร์จะเป็นคลาส/โครงสร้างที่คุณต้องการเริ่มต้น จากนั้นเปลี่ยนเทมเพลตคลาสนั้นเพื่อรวมอินสแตนซ์คงที่ของตัวเริ่มต้น สุดท้าย ให้ Constructor ของคลาส Initializer ทริกเกอร์โค้ดที่ทำการเริ่มต้นจริง

บางทีคุณควรป้องกันการเข้าถึง [1] ให้กับคลาส Initializer เพื่อไม่ให้การเริ่มต้นเกิดขึ้นมากกว่าหนึ่งครั้ง

อย่างที่คุณเห็น แนวคิดก็คือการใช้ข้อเท็จจริงที่ว่าคลาสอินสแตนซ์ได้รับการเรียกโค้ดคอนสตรัคเตอร์ อินสแตนซ์เทมเพลตได้รับการเตรียมใช้งานข้อมูลคงที่

ด้านล่างนี้เป็นการใช้งานที่เป็นไปได้ (และเรียบง่าย) โดยไม่มีเทมเพลต:

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

นี่เป็นอีกวิธีหนึ่ง คราวนี้เป็นการวางการกำหนดค่าเริ่มต้นใน struct โดยตรง:

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 ซึ่งอาจมีประโยชน์ที่นี่

person didierc    schedule 04.12.2012